diff --git a/README.md b/README.md index ddce749..eb89713 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,12 @@ This repository is participating in the 2023 edition of the [RoguelikeDev event] - Lots of mistakes in the tutorial--in Chapter 3, for example, the entire input handling code is missing imports and declarations. - I prefered implementing game states in `game-states.ts` as an enum to reference. -### Week 2 - 11 July 2023 - The object system and generating your first map +### [Week 2](https://www.reddit.com/r/roguelikedev/comments/14wuzqa/roguelikedev_does_the_complete_roguelike_tutorial/) - 11 July 2023 - The object system and generating your first map + +- Generics? Might be a bit out of my depth here. Currently unclear on how this is anything more than just typing the elements in `Struct.Table`? +- Class boilerplate is really verbose. Set up properties, then tag them in the constructor method arguments, then assign the arguments to the class properties. +- The setup to get a map working with collision detection is a lot faster and easier than the `libtcod` Python tutorial. The rooms-and-corridors method used there is useful, but most of these libraries have excellent map generation methods built-in, and getting something working should be the priority. This tutorial should lean on that too. +- Not a huge fan of just increasing the size of the generated map and terminal size by ~50% for no discernable reason. I assume there is a camera system incoming shortly, but this is going to require a massive refactor anyway, and this simple thing could have waited anyway. I opted to stick with the same size of screen as before, and lowered the number of random walls added to 200. ### Week 3 - 18 July 2023 - Field of view, placing enemies, and attacking diff --git a/src/main.ts b/src/main.ts index 4c4991c..e3542d3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { World, Entity } from 'ecsy'; import * as Components from './components'; import * as Systems from './systems'; import { GameStates } from './game-states'; +import { GameMap, TileType } from './map'; /** * The main game engine. @@ -24,6 +25,8 @@ export class Game { gameState: GameStates; + map = GameMap.CreateRandom(Game.WIDTH, Game.HEIGHT); + constructor() { this.world = new World(); this.terminal = this.createTerminal(); @@ -93,16 +96,16 @@ export class Game { // create an actions map of potential keypresses const actions = new Map void>(); actions.set(Input.KeyCode.LeftArrow, () => - tryMoveEntity(this.player, { x: -1, y: 0 }) + tryMoveEntity(this.player, { x: -1, y: 0 }, this) ); actions.set(Input.KeyCode.RightArrow, () => - tryMoveEntity(this.player, { x: 1, y: 0 }) + tryMoveEntity(this.player, { x: 1, y: 0 }, this) ); actions.set(Input.KeyCode.UpArrow, () => - tryMoveEntity(this.player, { x: 0, y: -1 }) + tryMoveEntity(this.player, { x: 0, y: -1 }, this) ); actions.set(Input.KeyCode.DownArrow, () => - tryMoveEntity(this.player, { x: 0, y: 1 }) + tryMoveEntity(this.player, { x: 0, y: 1 }, this) ); // if the keypair in the map matches an input key, run the command @@ -136,7 +139,7 @@ export class Game { * @param entity Entity with a Position component * @param delta Movement vector delta */ -function tryMoveEntity(entity: Entity, delta: Vector2) { +function tryMoveEntity(entity: Entity, delta: Vector2, game: Game) { const position = entity.getMutableComponent(Components.Position); if (!position) { @@ -144,8 +147,17 @@ function tryMoveEntity(entity: Entity, delta: Vector2) { return; } - position.x += delta.x; - position.y += delta.y; + // calculate the destination + const destination = { + x: position.x + delta.x, + y: position.y + delta.y, + }; + + // process the movement if the destination is a floor tile + if (game.map.tiles.get(destination) === TileType.Floor) { + position.x = destination.x; + position.y = destination.y; + } } // Modify the Window to include the Game object. diff --git a/src/map.ts b/src/map.ts new file mode 100644 index 0000000..0d22b1e --- /dev/null +++ b/src/map.ts @@ -0,0 +1,51 @@ +import { Struct, Rand } from "malwoden"; + +export enum TileType { + // all tiles should be truthy + Floor = 1, + Wall, +} + +export class GameMap { + tiles: Struct.Table; + width: number; + height: number; + + constructor(tiles: Struct.Table, width: number, height: number) { + this.tiles = tiles; + this.width = width; + this.height = height; + } + + static CreateRandom(width: number, height: number): GameMap { + const tiles = new Struct.Table(width, height); + const rng = new Rand.AleaRNG(); + const randomWalls = 200; + + // fill the whole map with floor tiles + tiles.fill(TileType.Floor); + + // fill the top and bottom with walls + for (let x = 0; x < width; x++) { + tiles.set({ x, y: 0 }, TileType.Wall); + tiles.set({ x, y: height - 1 }, TileType.Wall); + } + + // fill the left and right with walls + for (let y = 0; y < height; y++) { + tiles.set({ x: 0, y }, TileType.Wall); + tiles.set({ x: width - 1, y }, TileType.Wall); + } + + // fill random spots with walls + for (let i = 0; i < randomWalls; i++) { + const x = rng.nextInt(1, width - 2); + const y = rng.nextInt(1, height - 2); + + tiles.set({ x, y }, TileType.Wall); + } + + // return the new GameMap + return new GameMap(tiles, width, height); + } +} \ No newline at end of file diff --git a/src/systems/render.ts b/src/systems/render.ts index a82685e..dfa6b0b 100644 --- a/src/systems/render.ts +++ b/src/systems/render.ts @@ -1,7 +1,9 @@ +import { Color, Terminal } from 'malwoden'; import { World, System, SystemQueries } from 'ecsy'; import { Game } from '../main'; import * as Components from '../components'; +import { TileType } from '../map'; /** * Rendering system @@ -26,10 +28,29 @@ export class RenderSystem extends System { execute() { // run the query const { results } = this.queries.renderables; + + // declare new glyphs for the map + const floorGlyph = new Terminal.Glyph('.', Color.Green); + const wallGlyph = new Terminal.Glyph('#', Color.Green); // clear before the render pass this.game.terminal.clear(); + // draw map + for (let x = 0; x < this.game.map.width; x++) { + for (let y = 0; y < this.game.map.height; y++) { + // get the current tile + const tile = this.game.map.tiles.get({ x, y }); + + // draw the correct tile + if (tile === TileType.Floor) { + this.game.terminal.drawGlyph({ x, y }, floorGlyph); + } else if (tile === TileType.Wall) { + this.game.terminal.drawGlyph({ x, y }, wallGlyph); + } + } + } + // draw each entity for (const entity of results) { const position = entity.getComponent(Components.Position)!;