OK, fine, draw the map
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								src/art/pickups/stats.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/art/pickups/stats.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 643 B | 
| @@ -1,5 +1,6 @@ | ||||
| import {Color} from "./engine/datatypes.ts"; | ||||
|  | ||||
| export const BG_OUTER = Color.parseHexCode("#200500"); | ||||
| export const BG_OUTER = Color.parseHexCode("#143464"); | ||||
| export const BG_INSET = Color.parseHexCode("#242234"); | ||||
| export const FG_TEXT = Color.parseHexCode("#c0c0c0") | ||||
| export const FG_BOLD = Color.parseHexCode("#ffffff") | ||||
|   | ||||
							
								
								
									
										21
									
								
								src/drawpile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/drawpile.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| export class DrawPile { | ||||
|   readonly #draws: {depth: number, op: () => void}[] | ||||
|  | ||||
|   constructor() { | ||||
|     this.#draws = [] | ||||
|   } | ||||
|  | ||||
|   add(depth: number, op: () => void) { | ||||
|     this.#draws.push({depth, op}); | ||||
|   } | ||||
|  | ||||
|   execute() { | ||||
|     let draws = [...this.#draws]; | ||||
|     draws.sort( | ||||
|       (d0, d1) => d0.depth - d1.depth | ||||
|     ); | ||||
|     for (let d of draws.values()) { | ||||
|       d.op(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -66,6 +66,10 @@ export class Point { | ||||
|   negate() { | ||||
|     return new Point(-this.x, -this.y); | ||||
|   } | ||||
|  | ||||
|   equals(other: Point): boolean { | ||||
|     return this.x == other.x && this.y == other.y; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class Size { | ||||
| @@ -79,17 +83,19 @@ export class Size { | ||||
| } | ||||
|  | ||||
| export class Grid<T> { | ||||
|   data: T[][]; | ||||
|   readonly size: Size; | ||||
|   #data: T[][]; | ||||
|  | ||||
|   constructor({size, cbDefault}: {size: Size, cbDefault: (xy: Point) => T}) { | ||||
|     this.data = []; | ||||
|   constructor(size: Size, cbDefault: (xy: Point) => T) { | ||||
|     this.size = size; | ||||
|     this.#data = []; | ||||
|  | ||||
|     for (let y = 0; y < size.h; y++) { | ||||
|       let row = []; | ||||
|       for (let x = 0; x < size.w; x++) { | ||||
|         row.push(cbDefault(new Point(x, y))) | ||||
|       } | ||||
|       this.data.push(row); | ||||
|       this.#data.push(row); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -105,12 +111,12 @@ export class Grid<T> { | ||||
|       w = w1; | ||||
|     } | ||||
|  | ||||
|     return new Grid({ | ||||
|       size: new Size(w, h), | ||||
|       cbDefault: (xy) => { | ||||
|     return new Grid( | ||||
|       new Size(w, h), | ||||
|       (xy) => { | ||||
|         return ary[xy.y].charAt(xy.x); | ||||
|       } | ||||
|     }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   static createGridFromJaggedArray<T>(ary: Array<Array<T>>): Grid<T> { | ||||
| @@ -125,12 +131,35 @@ export class Grid<T> { | ||||
|       w = w1; | ||||
|     } | ||||
|  | ||||
|     return new Grid({ | ||||
|       size: new Size(w, h), | ||||
|       cbDefault: (xy) => { | ||||
|     return new Grid( | ||||
|       new Size(w, h), | ||||
|       (xy) => { | ||||
|         return ary[xy.y][xy.x]; | ||||
|       } | ||||
|     }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   map<T2>(cbCell: (content: T, position: Point) => T2) { | ||||
|     return new Grid(this.size, (xy) => cbCell(this.get(xy), xy)); | ||||
|   } | ||||
|  | ||||
|   #checkPosition(position: Point) { | ||||
|     if ( | ||||
|       (position.x < 0 || position.x >= this.size.w || Math.floor(position.x) != position.x) || | ||||
|       (position.y < 0 || position.y >= this.size.h || Math.floor(position.y) != position.y) | ||||
|     ) { | ||||
|       throw `invalid position for ${this.size}: ${position}` | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get(position: Point): T { | ||||
|     this.#checkPosition(position); | ||||
|     return this.#data[position.y][position.x]; | ||||
|   } | ||||
|  | ||||
|   set(position: Point, value: T) { | ||||
|     this.#checkPosition(position); | ||||
|     this.#data[position.y][position.x] = value; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -41,7 +41,6 @@ export class Sprite { | ||||
|     let srcCy = Math.floor(ix / this.cellsPerSheet.w); | ||||
|     let srcPx = srcCx * this.pixelsPerSubimage.w; | ||||
|     let srcPy = srcCy * this.pixelsPerSubimage.h; | ||||
|     console.log(`src px and py ${srcPx} ${srcPy}`) | ||||
|     ctx.drawImage(me, srcPx, srcPy, this.pixelsPerSubimage.w, this.pixelsPerSubimage.h, 0, 0, this.pixelsPerSubimage.w, this.pixelsPerSubimage.h); | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										145
									
								
								src/game.ts
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								src/game.ts
									
									
									
									
									
								
							| @@ -1,13 +1,15 @@ | ||||
| import {desiredHeight, desiredWidth, getScreen} from "./engine/internal/screen.ts"; | ||||
| import {BG_OUTER, FG_TEXT} from "./colors.ts"; | ||||
| import {BG_INSET, BG_OUTER, FG_TEXT} from "./colors.ts"; | ||||
| import {checkGrid, ConceptualCell, maps, mapSzX, mapSzY} from "./maps.ts"; | ||||
| import {D, I} from "./engine/public.ts"; | ||||
| import {IGame, Point, Size} from "./engine/datatypes.ts"; | ||||
| import {Grid, IGame, Point, Size} from "./engine/datatypes.ts"; | ||||
| import {sprRaccoonWalking, sprStatPickup} from "./sprites.ts"; | ||||
| import {DrawPile} from "./drawpile.ts"; | ||||
|  | ||||
| class MenuCamera { | ||||
|   // measured in whole screens | ||||
|   position: Point; | ||||
|   target: Point | ||||
|   target: Point; | ||||
|  | ||||
|   constructor({position, target}: {position: Point, target: Point}) { | ||||
|     this.position = position; | ||||
| @@ -43,6 +45,7 @@ export class Game implements IGame { | ||||
|   camera: MenuCamera; | ||||
|   state: GameState; | ||||
|   huntMode: HuntMode; | ||||
|   frame: number; | ||||
|  | ||||
|   constructor() { | ||||
|     this.camera = new MenuCamera({ | ||||
| @@ -52,9 +55,11 @@ export class Game implements IGame { | ||||
|     this.state = "Gameplay"; | ||||
|  | ||||
|     this.huntMode = HuntMode.generate({depth: 1}); | ||||
|     this.frame = 0; | ||||
|   } | ||||
|  | ||||
|   update() { | ||||
|     this.frame += 1; | ||||
|     if (I.isKeyPressed("w")) { | ||||
|       this.state = "Gameplay" | ||||
|     } | ||||
| @@ -125,8 +130,89 @@ export class Game implements IGame { | ||||
|   drawGameplay() { | ||||
|     let region = this.getPaneRegionForGameState("Gameplay") | ||||
|     // TODO: Draw | ||||
|     let oldCamera = D.camera; | ||||
|     D.camera = D.camera.offset(region.small.position.negate()); | ||||
|  | ||||
|     D.drawText("hello", region.small.position, FG_TEXT); | ||||
|     let drawpile = new DrawPile(); | ||||
|  | ||||
|     let globalOffset = | ||||
|       new Point(this.huntMode.player.x * 32, this.huntMode.player.y * 32).offset( | ||||
|         new Point(-192, -128) | ||||
|       ) | ||||
|  | ||||
|  | ||||
|     for (let y = 0; y < mapSzY; y += 1) { | ||||
|       for (let x = 0; x < mapSzX; x += 1) { | ||||
|         let cellOffset = new Point(x * 32, y * 32).offset(globalOffset.negate()); | ||||
|         let cellData = this.huntMode.cells.get(new Point(x, y)) | ||||
|  | ||||
|         this.#drawMapCell(drawpile, cellOffset, new Point(x, y), cellData); | ||||
|       } | ||||
|     } | ||||
|     this.#drawPlayer(drawpile, globalOffset); | ||||
|  | ||||
|     drawpile.execute(); | ||||
|  | ||||
|     D.drawText("hello", new Point(0, 0), FG_TEXT); | ||||
|  | ||||
|     D.camera = oldCamera; | ||||
|   } | ||||
|  | ||||
|   #drawMapCell( | ||||
|     drawpile: DrawPile, | ||||
|     cellOffset: Point, | ||||
|     mapPosition: Point, | ||||
|     cellData: MapCell, | ||||
|   ) { | ||||
|     const OFFSET_FLOOR = -256; | ||||
|     const OFFSET_AIR = 0; | ||||
|     const depth = cellOffset.y; | ||||
|     const onFloor = OFFSET_FLOOR + depth; | ||||
|     const inAir = OFFSET_AIR + depth; | ||||
|  | ||||
|     if (cellData.content.type == "block") { | ||||
|       return; | ||||
|     } | ||||
|     /* | ||||
|     if (!cellData.revealed) { | ||||
|       return; | ||||
|     } | ||||
|      */ | ||||
|  | ||||
|     // draw inset zone | ||||
|     drawpile.add(onFloor, () => | ||||
|       D.fillRect(cellOffset.offset(new Size(-16, -26)), new Size(32, 32), BG_INSET) | ||||
|     ); | ||||
|  | ||||
|     /* | ||||
|     if (!cellData.revealed) { | ||||
|       // TODO: draw some kind of question mark | ||||
|       D.drawText("?", cellOffset.offset(new Point(12, 8)), FG_TEXT); | ||||
|       return | ||||
|     } | ||||
|      */ | ||||
|     if (cellData.content.type == "statPickup") { | ||||
|       let content = cellData.content; | ||||
|       let extraXOffset = 0; // Math.cos(this.frame / 80 + mapPosition.x + mapPosition.y) * 1; | ||||
|       let extraYOffset = 0; // Math.sin(this.frame / 50 + mapPosition.x * 2+ mapPosition.y * 0.75) * 6 - 3; | ||||
|       drawpile.add(inAir, () => { | ||||
|         D.drawSprite( | ||||
|           sprStatPickup, | ||||
|           cellOffset.offset(new Point(extraXOffset, extraYOffset)), | ||||
|           ALL_STATS.indexOf(content.stat) | ||||
|         ) | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   #drawPlayer(drawpile: DrawPile, globalOffset: Point) { | ||||
|     let cellOffset = new Point(this.huntMode.player.x * 32, this.huntMode.player.y * 32).offset(globalOffset.negate()) | ||||
|     drawpile.add(this.huntMode.player.y, () => { | ||||
|       D.drawSprite( | ||||
|         sprRaccoonWalking, | ||||
|         cellOffset | ||||
|       ) | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -135,19 +221,21 @@ const ALL_STATS: Array<Stat> = ["AGI", "INT", "CHA", "PSI"]; | ||||
| type MapCellContent = | ||||
|   {type: "statPickup", stat: Stat} | | ||||
|   {type: "stairs"} | | ||||
|   {type: "empty"} | | ||||
|   {type: "block"} | ||||
|  | ||||
| type MapCell = { | ||||
|   content: MapCellContent, | ||||
|   isValidSpawn: boolean, | ||||
|   revealed: boolean | ||||
| } | ||||
|  | ||||
| class HuntMode { | ||||
|   depth: number | ||||
|   cells: Array<Array<MapCell>> | ||||
|   player: Point | null | ||||
|   cells: Grid<MapCell> | ||||
|   player: Point | ||||
|  | ||||
|   constructor({depth, cells, player}: {depth: number, cells: Array<Array<MapCell>>, player: Point | null}) { | ||||
|   constructor({depth, cells, player}: {depth: number, cells: Grid<MapCell>, player: Point }) { | ||||
|     this.depth = depth; | ||||
|     this.cells = cells; | ||||
|     this.player = player; | ||||
| @@ -160,44 +248,55 @@ class HuntMode { | ||||
|     let mapName = mapNames[Math.floor(Math.random() * mapNames.length)]; | ||||
|     let map = maps[mapName]; | ||||
|  | ||||
|     let rows = []; | ||||
|     for (let y = 0; y < mapSzY; y++) { | ||||
|       let row = []; | ||||
|       for (let x = 0; x < mapSzX; x++) { | ||||
|         let src = map[y][x]; | ||||
|         row.push(HuntMode.#generateCell(src)) | ||||
|     let cells = map.map((ccell, _xy) => { | ||||
|       return this.#generateCell(ccell); | ||||
|     }) | ||||
|  | ||||
|     let validSpawns = []; | ||||
|     for (let x = 0; x < cells.size.w; x++) { | ||||
|       for (let y = 0; y < cells.size.h; y++) { | ||||
|         let position = new Point(x, y); | ||||
|         if (cells.get(position).isValidSpawn)  { | ||||
|           validSpawns.push(position); | ||||
|         } | ||||
|       } | ||||
|       rows.push(row); | ||||
|     } | ||||
|     let player = choose(validSpawns); | ||||
|     cells.get(player).content = {type: "empty"}; | ||||
|  | ||||
|     if (Math.random() < 0.75) { | ||||
|       while (true) { | ||||
|         let x = Math.floor(Math.random() * mapSzX); | ||||
|         let y = Math.floor(Math.random() * mapSzY); | ||||
|         let xy = new Point(x, y); | ||||
|  | ||||
|         let item = rows[y][x]; | ||||
|         if (item.content.type != "block") { | ||||
|           item.content = {type: "stairs"} | ||||
|           break; | ||||
|         let item = cells.get(new Point(x, y)); | ||||
|         if (player.equals(xy)) { | ||||
|           continue; | ||||
|         } | ||||
|         if (item.content.type == "block") { | ||||
|           continue; | ||||
|         } | ||||
|         item.content = {type: "stairs"} | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return new HuntMode({depth, cells: rows, player: null}) | ||||
|     return new HuntMode({depth, cells, player}) | ||||
|   } | ||||
|  | ||||
|   static #generateCell(conceptual: ConceptualCell): MapCell { | ||||
|     switch (conceptual) { | ||||
|       case "X": | ||||
|         return { content: {type: "block"}, revealed: true}; | ||||
|         return { content: {type: "block"}, revealed: true, isValidSpawn: false}; | ||||
|       case " ": | ||||
|         return { content: HuntMode.#generateContent(true), revealed: true }; | ||||
|         return { content: HuntMode.#generateContent(), revealed: false, isValidSpawn: false }; | ||||
|       case ".": | ||||
|         return { content: HuntMode.#generateContent(false), revealed: true }; | ||||
|         return { content: HuntMode.#generateContent(), revealed: false, isValidSpawn: true }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static #generateContent(_revealed: boolean): MapCellContent { | ||||
|   static #generateContent(): MapCellContent { | ||||
|     // stat pickup | ||||
|     let gsp = (): MapCellContent => { | ||||
|       return {type: "statPickup", stat: choose(ALL_STATS)} | ||||
|   | ||||
							
								
								
									
										47
									
								
								src/maps.ts
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								src/maps.ts
									
									
									
									
									
								
							| @@ -1,43 +1,30 @@ | ||||
| import {Grid} from "./engine/datatypes.ts"; | ||||
|  | ||||
| export const mapSzX = 12; | ||||
| export const mapSzY= 9; | ||||
|  | ||||
| export function checkGrid<T>(cells: Array<Array<T>>): Array<Array<T>> { | ||||
|   if (cells.length != mapSzY) { | ||||
|     throw `map must be ${mapSzX}x${mapSzY}` | ||||
| export function checkGrid<T>(grid: Grid<T>): Grid<T> { | ||||
|   if (grid.size.w != mapSzX || grid.size.h != mapSzY) { | ||||
|     throw `map must be ${mapSzX}x${mapSzY}, not ${grid.size}` | ||||
|   } | ||||
|  | ||||
|   for (let row of cells.values()) { | ||||
|     if (row.length != mapSzX) { | ||||
|       throw `map must be ${mapSzX}x${mapSzY}`; | ||||
|     } | ||||
|   } | ||||
|   return cells; | ||||
|   return grid; | ||||
| } | ||||
|  | ||||
| export type ConceptualCell = "X" | "." | " "; | ||||
|  | ||||
| function loadMap(map: Array<string>): Array<Array<ConceptualCell>> { | ||||
|   let newRows: Array<Array<ConceptualCell>> = []; | ||||
|   for (let oldRow of map.values()) { | ||||
|     let newRow: Array<ConceptualCell> = [] | ||||
|     for (let i = 0; i < oldRow.length; i++) { | ||||
|       let char = oldRow.charAt(i); | ||||
|       let cell: ConceptualCell | ||||
|       switch(char) { | ||||
|         case "X": cell = "X"; break; | ||||
|         case ".": cell = "."; break; | ||||
|         case " ": cell = " "; break; | ||||
|         default: | ||||
|           throw `map element not valid: ${char}` | ||||
|       } | ||||
|       newRow.push(cell); | ||||
|     } | ||||
|     newRows.push(newRow); | ||||
|   } | ||||
|   return checkGrid(newRows); | ||||
| function loadMap(map: Array<string>): Grid<ConceptualCell> { | ||||
|   let src = Grid.createGridFromStringArray(map); | ||||
|   return src.map((char: string): ConceptualCell => { | ||||
|     switch(char) { | ||||
|       case "X": return "X"; | ||||
|       case ".": return "."; | ||||
|       case " ": return " "; | ||||
|       default: | ||||
|         throw `map element not valid: ${char}` | ||||
|   }}); | ||||
| } | ||||
|  | ||||
| export let maps: Record<string, Array<Array<ConceptualCell>>> = { | ||||
| export let maps: Record<string, Grid<ConceptualCell>> = { | ||||
|   map0: loadMap([ | ||||
|     "XX        XX", | ||||
|     "X.        .X", | ||||
|   | ||||
| @@ -1,14 +1,34 @@ | ||||
| import {Sprite} from "./engine/internal/sprite.ts"; | ||||
| /* | ||||
| import imgBat from "./art/characters/bat.png"; | ||||
| import imgKobold from "./art/characters/kobold.png"; | ||||
| import imgRaccoon from "./art/characters/raccoon.png"; | ||||
| import imgRaccoonWalking from "./art/characters/raccoon_walking.png"; | ||||
| import imgRobot from "./art/characters/robot.png"; | ||||
| import imgSnake from "./art/characters/snake.png"; | ||||
|  */ | ||||
| import imgRaccoon from "./art/characters/raccoon.png"; | ||||
| import imgRaccoonWalking from "./art/characters/raccoon_walking.png"; | ||||
| import imgStatPickup from "./art/pickups/stats.png"; | ||||
| import {Point, Size} from "./engine/datatypes.ts"; | ||||
|  | ||||
| /* | ||||
| export let sprBat = new Sprite(imgBat, 64, 64, 32, 32, 1, 1, 1); | ||||
| export let sprKobold = new Sprite(imgKobold, 64, 64, 32, 32, 1, 1, 1); | ||||
| export let sprRaccoon = new Sprite(imgRaccoon, 64, 64, 32, 32, 1, 1, 1); | ||||
| export let sprRaccoonWalking = new Sprite(imgRaccoonWalking, 64, 64, 32, 32, 8, 1, 8); | ||||
| export let sprRobot = new Sprite(imgRobot, 64, 64, 32, 32, 1, 1, 1); | ||||
| export let sprSnake = new Sprite(imgSnake, 64, 64, 32, 32, 1, 1, 1); | ||||
|  */ | ||||
|  | ||||
| export let sprRaccoon = new Sprite( | ||||
|   imgRaccoon, | ||||
|   new Size(64, 64), new Point(32, 32), new Size(1, 1), | ||||
|   1 | ||||
| ); | ||||
| export let sprRaccoonWalking = new Sprite( | ||||
|   imgRaccoonWalking, | ||||
|   new Size(64, 64), new Point(32, 32), new Size(8, 1), | ||||
|   8 | ||||
| ); | ||||
|  | ||||
| export let sprStatPickup = new Sprite( | ||||
|   imgStatPickup, new Size(32, 32), new Point(16, 26), | ||||
|   new Size(4, 1), 4 | ||||
| ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user