brick-engine-cli 1.0.13 → 1.0.15

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.
@@ -53,6 +53,15 @@ async function publishCommand(options) {
53
53
  type: "input",
54
54
  name: "gameName",
55
55
  message: "Enter your game name:",
56
+ validate: (input) => {
57
+ if (!input || input.trim().length === 0) {
58
+ return "Game name is required.";
59
+ }
60
+ if (input.length > 10) {
61
+ return "Game name cannot exceed 10 characters.";
62
+ }
63
+ return true;
64
+ },
56
65
  });
57
66
  prompts.push({
58
67
  type: "input",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brick-engine-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "CLI to scaffold Brick Game projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@types/inquirer": "^8.2.12",
16
- "brick-engine-js": "^1.0.31",
16
+ "brick-engine-js": "^1.0.40",
17
17
  "chalk": "^4.1.2",
18
18
  "commander": "^11.1.0",
19
19
  "dotenv": "^17.3.1",
@@ -66,6 +66,15 @@ export async function publishCommand(options: { url?: string; key?: string }) {
66
66
  type: "input",
67
67
  name: "gameName",
68
68
  message: "Enter your game name:",
69
+ validate: (input: string) => {
70
+ if (!input || input.trim().length === 0) {
71
+ return "Game name is required.";
72
+ }
73
+ if (input.length > 10) {
74
+ return "Game name cannot exceed 10 characters.";
75
+ }
76
+ return true;
77
+ },
69
78
  });
70
79
 
71
80
  prompts.push({
@@ -8,9 +8,18 @@ import {
8
8
  FontVerticalAlign,
9
9
  Color,
10
10
  Cell,
11
+ ScoreProperty,
11
12
  } from 'brick-engine-js';
12
13
 
14
+ /**
15
+ * This is the main class of your game.
16
+ * It extends 'Game', which provides the lifecycle and access to the engine's modules.
17
+ */
13
18
  export default class MyGame extends Game {
19
+ /**
20
+ * Initial player state.
21
+ * 'Cell' represents a single square on the grid.
22
+ */
14
23
  private initialPlayerCell: Cell = {
15
24
  value: 1,
16
25
  color: Color.CYAN,
@@ -19,38 +28,47 @@ export default class MyGame extends Game {
19
28
  y: 17,
20
29
  },
21
30
  };
22
- private initialTickInterval = 200;
23
31
 
24
- private player: Cell = this.initialPlayerCell;
32
+ // Initial difficulty settings
33
+ private initialSpawnEnemyInterval = 500;
34
+ private spawnEnemyInterval = 500;
25
35
 
36
+ private initialMoveEnemiesDownInterval = 100;
37
+ private moveEnemiesDownInterval = 100;
38
+
39
+ // Difficulty increase rates as level goes up
40
+ private increaseMoveEnemiesDownIntervalRate = 5;
41
+ private increaseSpawnEnemyIntervalRate = 25;
42
+
43
+ // Current game state
44
+ private player: Cell = this.initialPlayerCell;
26
45
  private enemies: Cell[] = [];
27
46
  private enemyColor = Color.RED;
28
47
  private enemyValue = 2;
29
48
 
30
- private spawnRate = 5; // Ticks between spawns
31
-
32
49
  /**
33
- * Called once after the engine and its modules are fully initialized.
50
+ * setupGame() is called once when the engine is initialized.
51
+ * Use this to configure controls, data persistence, and initial events.
34
52
  */
35
53
  setupGame(): void {
36
- const { control, session, grid, score, time } = this.modules;
54
+ // Destructure only the modules we need
55
+ const { control, session, grid, score, sound } = this.modules;
37
56
 
38
57
  // Reset game local state when starting/restarting.
39
58
  this.player = this.initialPlayerCell;
40
- time.setTickInterval(this.initialTickInterval);
41
59
 
42
- // Register how game data should be serialized and deserialized for the session
60
+ /**
61
+ * The 'session' module allows saving and loading game state.
62
+ * This is useful so the player doesn't lose progress on page refresh.
63
+ */
43
64
  session.register({
44
- // Unique identifier for the session data
45
- serialId: 'game-data',
46
- // Recieves from LocalStorage and restore the game state
65
+ serialId: 'game-data', // Unique identifier for this data set
47
66
  deserialize: (data: string) => {
48
67
  if (!data) return;
49
68
  const { player, enemies } = JSON.parse(data);
50
69
  this.player = player;
51
70
  this.enemies = enemies;
52
71
  },
53
- // Prepare data to be saved to LocalStorage
54
72
  serialize: () => {
55
73
  return JSON.stringify({
56
74
  player: this.player,
@@ -59,7 +77,12 @@ export default class MyGame extends Game {
59
77
  },
60
78
  });
61
79
 
62
- // Move Left
80
+ /**
81
+ * Control Configuration.
82
+ * Subscribe functions to be executed when specific keys are pressed or held.
83
+ */
84
+
85
+ // Move Left (single press)
63
86
  control.subscribeForPlayingScreen(
64
87
  ControlKey.LEFT,
65
88
  ControlEventType.PRESSED,
@@ -68,7 +91,7 @@ export default class MyGame extends Game {
68
91
  },
69
92
  );
70
93
 
71
- // Move Right
94
+ // Move Right (single press)
72
95
  control.subscribeForPlayingScreen(
73
96
  ControlKey.RIGHT,
74
97
  ControlEventType.PRESSED,
@@ -76,21 +99,91 @@ export default class MyGame extends Game {
76
99
  this.player = grid.moveCellRight(this.player);
77
100
  },
78
101
  );
102
+
103
+ // Move Left (held down)
104
+ control.subscribeForPlayingScreen(
105
+ ControlKey.LEFT,
106
+ ControlEventType.HELD,
107
+ () => {
108
+ this.player = grid.moveCellLeft(this.player);
109
+ },
110
+ );
111
+
112
+ // Move Right (held down)
113
+ control.subscribeForPlayingScreen(
114
+ ControlKey.RIGHT,
115
+ ControlEventType.HELD,
116
+ () => {
117
+ this.player = grid.moveCellRight(this.player);
118
+ },
119
+ );
120
+
121
+ /**
122
+ * Listen for score changes to increase level and difficulty.
123
+ */
124
+ score.subscribe(ScoreProperty.SCORE, () => {
125
+ // Increase speed every 50 points
126
+ if (
127
+ score.score > 0 &&
128
+ score.score % 50 === 0 &&
129
+ score.level < score.maxLevel
130
+ ) {
131
+ // Adjust time intervals to make the game faster
132
+ this.moveEnemiesDownInterval =
133
+ this.initialMoveEnemiesDownInterval -
134
+ score.level * this.increaseMoveEnemiesDownIntervalRate;
135
+
136
+ this.spawnEnemyInterval =
137
+ this.initialSpawnEnemyInterval -
138
+ score.level * this.increaseSpawnEnemyIntervalRate;
139
+
140
+ // Increase level in the score module
141
+ score.increaseLevel(1);
142
+ }
143
+ });
144
+
145
+ // Play different sounds as the level increases
146
+ score.subscribe(ScoreProperty.LEVEL, () => {
147
+ if (score.level < 6) {
148
+ sound.play(Sound.SCORE_2);
149
+ } else {
150
+ sound.play(Sound.SCORE_3);
151
+ }
152
+ });
79
153
  }
80
154
 
81
155
  /**
82
- * Logic update on every game "tick".
156
+ * update() is the main game logic loop.
157
+ * It runs every frame to process movement, collisions, and render to the grid.
83
158
  */
84
159
  update(): void {
85
160
  const { grid, state, score, sound, time } = this.modules;
86
161
 
162
+ /**
163
+ * The 'time' module handles timing deterministically.
164
+ * time.every(ms, callback) runs the function every millisecond interval.
165
+ */
166
+
87
167
  // 1. Move enemies down
88
- this.enemies = this.enemies
89
- .filter(enemy => enemy.coordinate.y !== grid.bottomRow)
90
- .map(enemy => grid.moveCellDown(enemy));
168
+ time.every(this.moveEnemiesDownInterval, () => {
169
+ this.enemies = this.enemies
170
+ .filter(enemy => enemy.coordinate.y !== grid.bottomRow) // Remove if they reach the bottom
171
+ .map(enemy => grid.moveCellDown(enemy)); // Move them down
172
+
173
+ // 2. Check collisions between enemies and the player (or other occupied areas)
174
+ const collision = this.enemies.find(enemy =>
175
+ grid.isAreaOccupied([enemy.coordinate]),
176
+ );
177
+
178
+ if (collision) {
179
+ sound.play(Sound.EXPLOSION);
180
+ state.triggerGameOver(); // End the game
181
+ return;
182
+ }
183
+ });
91
184
 
92
- // 2. Spawn new enemy
93
- if (time.isTickEvery(this.spawnRate)) {
185
+ // 3. Spawn new enemies at random positions at the top
186
+ time.every(this.spawnEnemyInterval, () => {
94
187
  const spawnX = Math.floor(Math.random() * grid.width);
95
188
 
96
189
  const newEnemy = {
@@ -103,61 +196,34 @@ export default class MyGame extends Game {
103
196
  };
104
197
 
105
198
  this.enemies.push(newEnemy);
106
- }
107
-
108
- // 3. Check collisions
109
- const collision = this.enemies.find(enemy =>
110
- grid.isAreaOccupied([enemy.coordinate]),
111
- );
112
-
113
- if (collision) {
114
- sound.play(Sound.EXPLOSION);
115
- state.triggerGameOver();
116
- return;
117
- }
199
+ });
118
200
 
119
- // 4. Update Grid
201
+ /**
202
+ * Grid Rendering.
203
+ * Clear the grid first, then 'stamp' objects onto it.
204
+ */
120
205
  grid.resetGrid();
121
206
 
122
207
  // Draw Player
123
- // Cell is a class that represents a single cell in the grid
124
208
  grid.stampCell(this.player);
125
209
 
126
- // Draw Enemies
127
- // Piece is a class that represents a collection of cells in the grid
210
+ // Draw all enemies
211
+ // grid.stampPiece accepts a collection of cells
128
212
  grid.stampPiece(this.enemies);
129
213
 
130
- // 5. Increase score and difficulty
131
- score.increaseScore(1);
132
-
133
- // Increase speed every 50 points, limit to 10 levels
134
- if (
135
- score.score > 0 &&
136
- score.score % 50 === 0 &&
137
- score.level < score.maxLevel
138
- ) {
139
- // Set tick interval to 200ms minus 15ms per level
140
- time.setTickInterval(this.initialTickInterval - 15 * score.level);
141
-
142
- // Increase level
143
- score.increaseLevel(1);
144
-
145
- // Play sound based on level
146
- if (score.level < 6) {
147
- sound.play(Sound.SCORE_2);
148
- } else {
149
- sound.play(Sound.SCORE_3);
150
- }
151
- }
214
+ // 4. Increase score automatically over time
215
+ time.every(this.moveEnemiesDownInterval, () => {
216
+ score.increaseScore(1);
217
+ });
152
218
  }
153
219
 
154
220
  /**
155
- * Visual-only rendering (HUD elements).
221
+ * render() is used for visual-only elements that don't affect collision (e.g., HUD).
156
222
  */
157
223
  render(): void {}
158
224
 
159
225
  /**
160
- * Title Screen Layout.
226
+ * Draw the title screen interface.
161
227
  */
162
228
  drawTitleScreen(): void {
163
229
  const { text } = this.modules;
@@ -175,7 +241,7 @@ export default class MyGame extends Game {
175
241
  }
176
242
 
177
243
  /**
178
- * Game Over Screen Layout.
244
+ * Draw the Game Over screen interface.
179
245
  */
180
246
  drawGameOverScreen(): void {
181
247
  const { text, score } = this.modules;