@game_ryo/lsji 0.1.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.
Files changed (61) hide show
  1. package/.github/workflows/node.yml +46 -0
  2. package/AGENTS.md +143 -0
  3. package/LICENSE +185 -0
  4. package/PROPOSAL.md +18 -0
  5. package/README.md +102 -0
  6. package/bin/lsji.js +8 -0
  7. package/docs/README.md +43 -0
  8. package/docs/blog/2019-05-28-first-blog-post.mdx +12 -0
  9. package/docs/blog/2019-05-29-long-blog-post.mdx +44 -0
  10. package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  11. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  12. package/docs/blog/2021-08-26-welcome/index.mdx +29 -0
  13. package/docs/blog/authors.yml +25 -0
  14. package/docs/blog/tags.yml +19 -0
  15. package/docs/docs/api/agent.md +151 -0
  16. package/docs/docs/api/env.md +133 -0
  17. package/docs/docs/api/environments.md +102 -0
  18. package/docs/docs/api/qlearning.md +138 -0
  19. package/docs/docs/api/storage.md +168 -0
  20. package/docs/docs/architecture.md +155 -0
  21. package/docs/docs/cli.md +210 -0
  22. package/docs/docs/contributing.md +162 -0
  23. package/docs/docs/core-concepts.md +152 -0
  24. package/docs/docs/examples/advanced-training.md +244 -0
  25. package/docs/docs/examples/custom-environment.md +198 -0
  26. package/docs/docs/examples/custom-storage.md +251 -0
  27. package/docs/docs/getting-started.md +91 -0
  28. package/docs/docusaurus.config.ts +149 -0
  29. package/docs/package-lock.json +19522 -0
  30. package/docs/package.json +49 -0
  31. package/docs/sidebars.ts +33 -0
  32. package/docs/src/components/HomepageFeatures/index.tsx +71 -0
  33. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  34. package/docs/src/css/custom.css +79 -0
  35. package/docs/src/pages/index.module.css +23 -0
  36. package/docs/src/pages/index.tsx +44 -0
  37. package/docs/src/pages/markdown-page.mdx +7 -0
  38. package/docs/static/.nojekyll +0 -0
  39. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  40. package/docs/static/img/docusaurus.png +0 -0
  41. package/docs/static/img/favicon.ico +0 -0
  42. package/docs/static/img/logo.png +0 -0
  43. package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  44. package/docs/static/img/undraw_docusaurus_react.svg +170 -0
  45. package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  46. package/docs/tsconfig.json +12 -0
  47. package/legacy/worker.js +166 -0
  48. package/legacy/wrangler.toml +11 -0
  49. package/package.json +26 -0
  50. package/src/cli.js +232 -0
  51. package/src/core/agent.js +239 -0
  52. package/src/core/env.js +86 -0
  53. package/src/core/qlearning.js +197 -0
  54. package/src/envs/rps.js +168 -0
  55. package/src/index.js +22 -0
  56. package/src/storage/better-sqlite.js +133 -0
  57. package/src/storage/index.js +146 -0
  58. package/src/storage/memory.js +98 -0
  59. package/src/storage/sqlite.js +123 -0
  60. package/test/core/qlearning.test.js +150 -0
  61. package/test/storage/memory.test.js +81 -0
@@ -0,0 +1,25 @@
1
+ yangshun:
2
+ name: Yangshun Tay
3
+ title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
4
+ url: https://linkedin.com/in/yangshun
5
+ image_url: https://github.com/yangshun.png
6
+ page: true
7
+ socials:
8
+ x: yangshunz
9
+ linkedin: yangshun
10
+ github: yangshun
11
+ newsletter: https://www.greatfrontend.com
12
+
13
+ slorber:
14
+ name: Sébastien Lorber
15
+ title: Docusaurus maintainer
16
+ url: https://sebastienlorber.com
17
+ image_url: https://github.com/slorber.png
18
+ page:
19
+ # customize the url of the author page at /blog/authors/<permalink>
20
+ permalink: '/all-sebastien-lorber-articles'
21
+ socials:
22
+ x: sebastienlorber
23
+ linkedin: sebastienlorber
24
+ github: slorber
25
+ newsletter: https://thisweekinreact.com
@@ -0,0 +1,19 @@
1
+ facebook:
2
+ label: Facebook
3
+ permalink: /facebook
4
+ description: Facebook tag description
5
+
6
+ hello:
7
+ label: Hello
8
+ permalink: /hello
9
+ description: Hello tag description
10
+
11
+ docusaurus:
12
+ label: Docusaurus
13
+ permalink: /docusaurus
14
+ description: Docusaurus tag description
15
+
16
+ hola:
17
+ label: Hola
18
+ permalink: /hola
19
+ description: Hola tag description
@@ -0,0 +1,151 @@
1
+ ---
2
+ title: Agent API
3
+ description: High-level agent orchestration
4
+ ---
5
+
6
+ # Agent API
7
+
8
+ The `Agent` class provides high-level orchestration of Q-Learning, Storage, and Environment.
9
+
10
+ ## Import
11
+
12
+ ```typescript
13
+ import { Agent } from 'lsji';
14
+ ```
15
+
16
+ ## Constructor
17
+
18
+ ```typescript
19
+ const agent = new Agent({
20
+ qlearning: QLearning, // Required: Q-Learning engine
21
+ storage: Storage, // Required: Storage backend
22
+ env: Env // Optional: Environment for train/play
23
+ });
24
+ ```
25
+
26
+ ## Methods
27
+
28
+ ### `start()`
29
+ Enable the system for training and play.
30
+
31
+ ```typescript
32
+ await agent.start();
33
+ // Returns: { status: 'success', message: 'System STARTED' }
34
+ ```
35
+
36
+ ### `stop()`
37
+ Disable the system (pauses training and play).
38
+
39
+ ```typescript
40
+ await agent.stop();
41
+ // Returns: { status: 'success', message: 'System STOPPED' }
42
+ ```
43
+
44
+ ### `status()`
45
+ Get current system status and statistics.
46
+
47
+ ```typescript
48
+ const status = await agent.status();
49
+ // Returns:
50
+ {
51
+ status: 'running' | 'stopped',
52
+ todayTotal: number, // Battles today
53
+ limit: 90000, // Daily limit
54
+ performance: [ // Per-mode statistics
55
+ { mode: 'train', total: 100, win_rate: 65.5 },
56
+ { mode: 'test', total: 50, win_rate: 72.0 }
57
+ ],
58
+ aiBrain: [ // Full Q-table
59
+ { state: '0', action: 0, q_value: 0.45 },
60
+ { state: '0', action: 1, q_value: 0.12 },
61
+ ...
62
+ ]
63
+ }
64
+ ```
65
+
66
+ ### `train(options)`
67
+ Train the agent.
68
+
69
+ ```typescript
70
+ const result = await agent.train({
71
+ episodes: 200, // Number of episodes (default: 200)
72
+ actionSelector: (episode, lastAction) => number, // Custom pattern (optional)
73
+ batchSize: 200 // DB batch size (default: 200)
74
+ });
75
+
76
+ // Returns:
77
+ {
78
+ episodes: 200,
79
+ wins: 85,
80
+ losses: 62,
81
+ draws: 53
82
+ }
83
+ ```
84
+
85
+ **Built-in Training Patterns:**
86
+ ```typescript
87
+ import { TrainingPattern, getTrainingAction } from 'lsji';
88
+
89
+ // Pattern 0: Random (default)
90
+ await agent.train({ episodes: 500 });
91
+
92
+ // Pattern 1: Always Rock
93
+ await agent.train({
94
+ episodes: 500,
95
+ actionSelector: (ep, last) => getTrainingAction(TrainingPattern.ALWAYS_ROCK, ep, last)
96
+ });
97
+
98
+ // Pattern 2: Counter
99
+ await agent.train({
100
+ episodes: 500,
101
+ actionSelector: (ep, last) => getTrainingAction(TrainingPattern.COUNTER, ep, last)
102
+ });
103
+
104
+ // Pattern 3: Sequential
105
+ await agent.train({
106
+ episodes: 500,
107
+ actionSelector: (ep, last) => getTrainingAction(TrainingPattern.SEQUENTIAL, ep, last)
108
+ });
109
+ ```
110
+
111
+ ### `play(options)`
112
+ Play a single step against the agent.
113
+
114
+ ```typescript
115
+ const result = await agent.play({
116
+ userAction: 0 // Optional: user action for envs that need it
117
+ });
118
+
119
+ // Returns:
120
+ {
121
+ action: 1, // Agent's chosen action
122
+ reward: 1, // Reward received
123
+ done: false, // Episode ended
124
+ info: { opponentAction: 0 } // Environment-specific info
125
+ }
126
+ ```
127
+
128
+ ### `setEnvironment(env)`
129
+ Inject or change the environment at runtime.
130
+
131
+ ```typescript
132
+ agent.setEnvironment(new MyCustomEnv());
133
+ ```
134
+
135
+ ## Example
136
+
137
+ ```typescript
138
+ import { Agent, QLearning, createStorage, RockPaperScissorsEnv } from 'lsji';
139
+
140
+ const storage = await createStorage('sqlite', { path: './agent.db' });
141
+ const qlearning = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
142
+ const env = new RockPaperScissorsEnv({ opponent: 'random' });
143
+
144
+ const agent = new Agent({ qlearning, storage, env });
145
+
146
+ await agent.train({ episodes: 1000 });
147
+ const result = await agent.play(0); // Play Rock
148
+ console.log(`AI played: ${result.action}, Result: ${result.reward > 0 ? 'WIN' : result.reward < 0 ? 'LOSE' : 'DRAW'}`);
149
+
150
+ await storage.close();
151
+ ```
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: Env Interface
3
+ description: Base environment interface for RL problems
4
+ ---
5
+
6
+ # Env Interface
7
+
8
+ All reinforcement learning environments must extend the abstract `Env` class.
9
+
10
+ ## Import
11
+
12
+ ```typescript
13
+ import { Env } from 'lsji';
14
+ ```
15
+
16
+ ## Abstract Methods
17
+
18
+ ### `getState()`
19
+ Return current state as a string key.
20
+
21
+ ```typescript
22
+ getState(): string;
23
+ ```
24
+
25
+ ### `step(action)`
26
+ Execute an action and return the result.
27
+
28
+ ```typescript
29
+ async step(action: number): Promise<StepResult>;
30
+ ```
31
+
32
+ **StepResult:**
33
+ ```typescript
34
+ interface StepResult {
35
+ state: string; // New state
36
+ reward: number; // Reward for this step
37
+ done: boolean; // Episode ended
38
+ info?: object; // Additional info
39
+ }
40
+ ```
41
+
42
+ ### `actionSize()`
43
+ Return number of possible actions.
44
+
45
+ ```typescript
46
+ actionSize(): number;
47
+ ```
48
+
49
+ ### `reset()`
50
+ Reset environment to initial state.
51
+
52
+ ```typescript
53
+ async reset(): Promise<string>; // Returns initial state
54
+ ```
55
+
56
+ ## Optional Methods
57
+
58
+ ### `render()`
59
+ Human-readable representation for debugging.
60
+
61
+ ```typescript
62
+ render(): string;
63
+ ```
64
+
65
+ ## StateEncoder Utility
66
+
67
+ Helper for encoding/decoding complex states:
68
+
69
+ ```typescript
70
+ import { StateEncoder } from 'lsji';
71
+
72
+ // Encode object to string
73
+ const key = StateEncoder.encode({ position: 5, inventory: ['sword'] });
74
+ // Returns: '{"position":5,"inventory":["sword"]}'
75
+
76
+ // Decode string to object
77
+ const state = StateEncoder.decode(key);
78
+ // Returns: { position: 5, inventory: ['sword'] }
79
+ ```
80
+
81
+ ## Creating a Custom Environment
82
+
83
+ ```typescript
84
+ import { Env } from 'lsji';
85
+
86
+ class GridWorldEnv extends Env {
87
+ constructor() {
88
+ super();
89
+ this.position = 0;
90
+ this.gridSize = 10;
91
+ }
92
+
93
+ getState() {
94
+ return String(this.position);
95
+ }
96
+
97
+ async step(action) {
98
+ // Actions: 0=left, 1=right
99
+ if (action === 0) this.position = Math.max(0, this.position - 1);
100
+ if (action === 1) this.position = Math.min(this.gridSize - 1, this.position + 1);
101
+
102
+ const done = this.position === this.gridSize - 1;
103
+ const reward = done ? 1 : -0.01;
104
+
105
+ return {
106
+ state: String(this.position),
107
+ reward,
108
+ done,
109
+ info: { position: this.position }
110
+ };
111
+ }
112
+
113
+ actionSize() {
114
+ return 2;
115
+ }
116
+
117
+ async reset() {
118
+ this.position = 0;
119
+ return '0';
120
+ }
121
+
122
+ render() {
123
+ return `GridWorld: position ${this.position}/${this.gridSize - 1}`;
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Best Practices
129
+
130
+ 1. **State as string** — Use simple string keys for Q-table indexing
131
+ 2. **Deterministic rewards** — Same state-action should give consistent rewards
132
+ 3. **Action space** — Keep action space small for tabular Q-learning
133
+ 4. **Reset** — Always implement proper reset for episode boundaries
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Built-in Environments
3
+ description: Pre-built environments for quick start
4
+ ---
5
+
6
+ # Built-in Environments
7
+
8
+ LSJI includes a Rock-Paper-Scissors environment for demonstration and testing.
9
+
10
+ ## Import
11
+
12
+ ```typescript
13
+ import {
14
+ RockPaperScissorsEnv,
15
+ TrainingPattern,
16
+ getTrainingAction
17
+ } from 'lsji';
18
+ ```
19
+
20
+ ## RockPaperScissorsEnv
21
+
22
+ Classic Rock-Paper-Scissors game environment.
23
+
24
+ ### Constructor
25
+
26
+ ```typescript
27
+ const env = new RockPaperScissorsEnv({
28
+ opponent: 'random' // 'random' | 'always_rock' | 'counter' | 'sequential'
29
+ });
30
+ ```
31
+
32
+ ### Opponent Strategies
33
+
34
+ | Strategy | Description |
35
+ |----------|-------------|
36
+ | `'random'` | Uniform random actions (default) |
37
+ | `'always_rock'` | Always plays Rock (0) |
38
+ | `'counter'` | Plays counter to agent's previous action |
39
+ | `'sequential'` | Cycles through Rock→Scissors→Paper |
40
+
41
+ ### Methods
42
+
43
+ All standard `Env` methods plus:
44
+
45
+ ```typescript
46
+ // Static helpers
47
+ RockPaperScissorsEnv.getHandName(0); // 'Rock'
48
+ RockPaperScissorsEnv.getHandName(1); // 'Scissors'
49
+ RockPaperScissorsEnv.getHandName(2); // 'Paper'
50
+
51
+ const { judge, reward, outcome } = RockPaperScissorsEnv.calculateOutcome(0, 2);
52
+ // judge: 2, reward: 1, outcome: 'WIN'
53
+ ```
54
+
55
+ ### Training Patterns
56
+
57
+ ```typescript
58
+ import { TrainingPattern, getTrainingAction } from 'lsji';
59
+
60
+ // Pattern IDs
61
+ TrainingPattern.RANDOM; // 0
62
+ TrainingPattern.ALWAYS_ROCK; // 1
63
+ TrainingPattern.COUNTER; // 2
64
+ TrainingPattern.SEQUENTIAL; // 3
65
+
66
+ // Get action for pattern
67
+ const action = getTrainingAction(TrainingPattern.COUNTER, episode, lastAction);
68
+ ```
69
+
70
+ ### Example
71
+
72
+ ```typescript
73
+ import {
74
+ Agent, QLearning, createStorage,
75
+ RockPaperScissorsEnv, TrainingPattern, getTrainingAction
76
+ } from 'lsji';
77
+
78
+ const storage = await createStorage('sqlite', { path: './rps.db' });
79
+ const qlearning = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
80
+
81
+ // Train against counter opponent
82
+ const env = new RockPaperScissorsEnv({ opponent: 'counter' });
83
+ const agent = new Agent({ qlearning, storage, env });
84
+
85
+ await agent.train({
86
+ episodes: 1000,
87
+ actionSelector: (ep, last) => getTrainingAction(TrainingPattern.RANDOM, ep, last)
88
+ });
89
+
90
+ // Play against random opponent
91
+ const playEnv = new RockPaperScissorsEnv({ opponent: 'random' });
92
+ agent.setEnvironment(playEnv);
93
+
94
+ const result = await agent.play(0); // You play Rock
95
+ console.log(`AI: ${RockPaperScissorsEnv.getHandName(result.action)} | ${result.reward > 0 ? 'WIN' : 'LOSE'}`);
96
+
97
+ await storage.close();
98
+ ```
99
+
100
+ ## Creating Custom Environments
101
+
102
+ See [Custom Environment Example](/docs/examples/custom-environment) for a complete guide.
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: QLearning API
3
+ description: Tabular Q-Learning engine with TD updates
4
+ ---
5
+
6
+ # QLearning API
7
+
8
+ The `QLearning` class implements tabular Q-Learning with epsilon-greedy exploration.
9
+
10
+ ## Import
11
+
12
+ ```typescript
13
+ import { QLearning } from 'lsji';
14
+ ```
15
+
16
+ ## Constructor
17
+
18
+ ```typescript
19
+ const qlearning = new QLearning({
20
+ alpha: 0.1, // Learning rate [0, 1] (default: 0.1)
21
+ gamma: 0.9, // Discount factor [0, 1] (default: 0.9)
22
+ epsilon: 0.1, // Exploration rate [0, 1] (default: 0.1)
23
+ storage: Storage // Required: Storage backend
24
+ });
25
+ ```
26
+
27
+ Values are automatically clamped to [0, 1] range.
28
+
29
+ ## Methods
30
+
31
+ ### `initialize()`
32
+ Load Q-table from storage. Called automatically by other methods.
33
+
34
+ ```typescript
35
+ await qlearning.initialize();
36
+ ```
37
+
38
+ ### `getQValue(state, action)`
39
+ Get Q-value for a state-action pair.
40
+
41
+ ```typescript
42
+ const value = qlearning.getQValue('state1', 0);
43
+ // Returns: number (0 if unseen)
44
+ ```
45
+
46
+ ### `setQValue(state, action, value)`
47
+ Set Q-value and persist to storage.
48
+
49
+ ```typescript
50
+ await qlearning.setQValue('state1', 0, 0.5);
51
+ ```
52
+
53
+ ### `act(state, actionSize)`
54
+ Select action using epsilon-greedy policy.
55
+
56
+ ```typescript
57
+ const action = await qlearning.act('state1', 3);
58
+ // Returns: number (0 to actionSize-1)
59
+ ```
60
+
61
+ **Behavior:**
62
+ - With probability `epsilon`: random action
63
+ - With probability `1-epsilon`: best known action (ties broken randomly)
64
+
65
+ ### `learn(state, action, reward, nextState, nextActionSize)`
66
+ Full TD update: Q(s,a) ← Q(s,a) + α[r + γ·maxₐ' Q(s',a') - Q(s,a)]
67
+
68
+ ```typescript
69
+ const newQ = await qlearning.learn('state1', 0, 1, 'state2', 3);
70
+ // Returns: updated Q-value
71
+ ```
72
+
73
+ ### `learnSimple(state, action, reward)`
74
+ Simplified update for terminal states: Q(s,a) ← Q(s,a) + α[r - Q(s,a)]
75
+
76
+ ```typescript
77
+ const newQ = await qlearning.learnSimple('state1', 0, 1);
78
+ // Returns: updated Q-value
79
+ ```
80
+
81
+ ### `reset()`
82
+ Clear in-memory Q-table cache.
83
+
84
+ ```typescript
85
+ await qlearning.reset();
86
+ // Note: Does not clear storage
87
+ ```
88
+
89
+ ### `getStateValues(state, actionSize)`
90
+ Get all Q-values for a state.
91
+
92
+ ```typescript
93
+ const values = qlearning.getStateValues('state1', 3);
94
+ // Returns: { 0: 0.5, 1: 0.2, 2: -0.1 }
95
+ ```
96
+
97
+ ### `getFullQTable()`
98
+ Get entire Q-table for inspection.
99
+
100
+ ```typescript
101
+ const table = await qlearning.getFullQTable();
102
+ // Returns:
103
+ [
104
+ { state: 'state1', action: 0, q_value: 0.5 },
105
+ { state: 'state1', action: 1, q_value: 0.2 },
106
+ ...
107
+ ]
108
+ ```
109
+
110
+ ## Hyperparameters
111
+
112
+ | Parameter | Default | Range | Description |
113
+ |-----------|---------|-------|-------------|
114
+ | `alpha` | 0.1 | [0, 1] | Learning rate — how much new info overrides old |
115
+ | `gamma` | 0.9 | [0, 1] | Discount factor — future reward importance |
116
+ | `epsilon` | 0.1 | [0, 1] | Exploration rate — random action probability |
117
+
118
+ ## Example
119
+
120
+ ```typescript
121
+ import { QLearning, createStorage } from 'lsji';
122
+
123
+ const storage = await createStorage('memory');
124
+ const ql = new QLearning({ alpha: 0.1, gamma: 0.9, epsilon: 0.1, storage });
125
+
126
+ await ql.initialize();
127
+
128
+ // Train on simple state-action pairs
129
+ await ql.learnSimple('state1', 0, 1); // Win
130
+ await ql.learnSimple('state1', 0, 1); // Win again
131
+ await ql.learnSimple('state1', 1, -1); // Lose
132
+
133
+ // Check learned values
134
+ console.log(ql.getQValue('state1', 0)); // ~0.19
135
+ console.log(ql.getQValue('state1', 1)); // ~-0.1
136
+
137
+ await storage.close();
138
+ ```