Cell.ts
1 import { AnimationManager } from './AnimationManager' 2 import { Direction } from './Direction' 3 import { Game } from './Game' 4 import { ImageManager } from './ImageManager' 5 import { Player } from './Player' 6 import { Point } from './Point' 7 import { Rotation } from './Rotation' 8 9 export const CELL_SIZE = 16 10 11 export abstract class Cell { 12 private readonly position: Point 13 private readonly animationManager: AnimationManager 14 15 protected constructor(position: Point) { 16 this.position = position 17 18 const image = ImageManager.getImage('tiles') 19 20 this.animationManager = new AnimationManager(image, CELL_SIZE, CELL_SIZE) 21 } 22 23 public update(dt: number): void { 24 this.animationManager.update(dt) 25 } 26 27 public render(ctx: CanvasRenderingContext2D): void { 28 ctx.save() 29 ctx.translate(this.position.x * CELL_SIZE, this.position.y * CELL_SIZE) 30 31 this.animationManager.render(ctx) 32 33 ctx.restore() 34 } 35 36 /** 37 * Évènement : avant que le joueur n'entre dans la case 38 */ 39 public onBeforePlayerIn(_player: Player): void { 40 // À surcharger 41 } 42 43 /** 44 * Évènement : lorsque le joueur est entièrement dans la case 45 * 46 * @return `this` si la case est inchangée ou `null` pour la supprimer 47 */ 48 public onAfterPlayerIn(_player: Player, _game: Game): this | null { 49 return this 50 } 51 52 /** 53 * Évènement : lorsque le joueur a quitté la case 54 */ 55 public onAfterPlayerOut(): void { 56 // À surcharger 57 } 58 59 /** 60 * Est-ce qu'on peut rentrer sur la case ? 61 */ 62 public canEnter?(_direction: Direction): boolean { 63 return false 64 } 65 66 /** 67 * Est-ce qu'on peut sortir de la case ? 68 */ 69 public canLeave?(_direction: Direction): boolean { 70 return false 71 } 72 73 public getPosition(): Point { 74 return this.position 75 } 76 77 protected getAnimationManager(): AnimationManager { 78 return this.animationManager 79 } 80 } 81 82 // Rocher 83 export class Stone extends Cell { 84 public constructor(position: Point) { 85 super(position) 86 87 this.getAnimationManager().addAnimation('idle', [ 0 ]) 88 89 this.getAnimationManager().play('idle') 90 } 91 92 public canEnter(): boolean { 93 return true 94 } 95 } 96 97 // Bouton 98 export class Button extends Cell { 99 private value: number 100 101 public constructor(position: Point, value: number) { 102 super(position) 103 104 this.value = value 105 106 this.getAnimationManager().addAnimation('activated', [ 80 ]) 107 this.getAnimationManager().addAnimation('deactivated-1', [ 81 ]) 108 this.getAnimationManager().addAnimation('deactivated-2', [ 82 ]) 109 this.getAnimationManager().addAnimation('deactivated-3', [ 83 ]) 110 111 this.getAnimationManager().play(`deactivated-${this.value}`) 112 } 113 114 public onAfterPlayerOut(): void { 115 this.value -= 1 116 117 if (this.value === 0) { 118 this.getAnimationManager().play('activated') 119 } else { 120 this.getAnimationManager().play(`deactivated-${this.value}`) 121 } 122 } 123 124 public canEnter(_direction: Direction): boolean { 125 return this.value === 0 126 } 127 } 128 129 // Tapis roulant 130 export class Conveyor extends Cell { 131 private readonly direction: Direction 132 133 public constructor(position: Point, direction: Direction) { 134 super(position) 135 136 this.direction = direction 137 138 this.getAnimationManager().addAnimation(Direction.Up.toString(), [ 30, 31, 32, 33 ], { 139 frameDuration: 0.08, 140 }) 141 142 this.getAnimationManager().addAnimation(Direction.Right.toString(), [ 40, 41, 42, 43 ], { 143 frameDuration: 0.08, 144 }) 145 146 this.getAnimationManager().addAnimation(Direction.Down.toString(), [ 50, 51, 52, 53 ], { 147 frameDuration: 0.08, 148 }) 149 150 this.getAnimationManager().addAnimation(Direction.Left.toString(), [ 60, 61, 62, 63 ], { 151 frameDuration: 0.08, 152 }) 153 154 this.getAnimationManager().play(direction.toString()) 155 } 156 157 public onAfterPlayerIn(player: Player, _game: Game): this | null { 158 player.move(this.direction, 'idle') 159 160 return this 161 } 162 } 163 164 // Tourniquet 165 export class Turnstile extends Cell { 166 private angle: Rotation 167 168 public constructor(position: Point, angle: Rotation) { 169 super(position) 170 171 this.angle = angle 172 173 this.getAnimationManager().addAnimation(Rotation.UpLeft.toString(), [ 70 ]) 174 this.getAnimationManager().addAnimation(Rotation.UpRight.toString(), [ 71 ]) 175 this.getAnimationManager().addAnimation(Rotation.DownRight.toString(), [ 72 ]) 176 this.getAnimationManager().addAnimation(Rotation.DownLeft.toString(), [ 73 ]) 177 178 this.getAnimationManager().addAnimation(Rotation.Horizontal.toString(), [ 74 ]) 179 this.getAnimationManager().addAnimation(Rotation.Vertical.toString(), [ 75 ]) 180 181 this.getAnimationManager().addAnimation(Rotation.Up.toString(), [ 76 ]) 182 this.getAnimationManager().addAnimation(Rotation.Right.toString(), [ 77 ]) 183 this.getAnimationManager().addAnimation(Rotation.Down.toString(), [ 78 ]) 184 this.getAnimationManager().addAnimation(Rotation.Left.toString(), [ 79 ]) 185 186 this.getAnimationManager().play(angle.toString()) 187 } 188 189 public onAfterPlayerOut(): void { 190 switch (this.angle) { 191 case Rotation.UpRight: 192 this.angle = Rotation.DownRight 193 break 194 195 case Rotation.UpLeft: 196 this.angle = Rotation.UpRight 197 break 198 199 case Rotation.DownRight: 200 this.angle = Rotation.DownLeft 201 break 202 203 case Rotation.DownLeft: 204 this.angle = Rotation.UpLeft 205 break 206 207 case Rotation.Vertical: 208 this.angle = Rotation.Horizontal 209 break 210 211 case Rotation.Horizontal: 212 this.angle = Rotation.Vertical 213 break 214 215 case Rotation.Up: 216 this.angle = Rotation.Right 217 break 218 219 case Rotation.Right: 220 this.angle = Rotation.Down 221 break 222 223 case Rotation.Down: 224 this.angle = Rotation.Left 225 break 226 227 case Rotation.Left: 228 this.angle = Rotation.Up 229 break 230 } 231 232 this.getAnimationManager().play(this.angle.toString()) 233 } 234 235 public canEnter(direction: Direction): boolean { 236 switch (this.angle) { 237 case Rotation.UpRight: 238 return [ Direction.Down, Direction.Left ].includes(direction) 239 240 case Rotation.UpLeft: 241 return [ Direction.Down, Direction.Right ].includes(direction) 242 243 case Rotation.DownRight: 244 return [ Direction.Up, Direction.Left ].includes(direction) 245 246 case Rotation.DownLeft: 247 return [ Direction.Up, Direction.Right ].includes(direction) 248 249 case Rotation.Vertical: 250 return [ Direction.Right, Direction.Left ].includes(direction) 251 252 case Rotation.Horizontal: 253 return [ Direction.Up, Direction.Down ].includes(direction) 254 255 case Rotation.Up: 256 return direction === Direction.Down 257 258 case Rotation.Right: 259 return direction === Direction.Left 260 261 case Rotation.Down: 262 return direction === Direction.Up 263 264 case Rotation.Left: 265 return direction === Direction.Right 266 } 267 } 268 269 public canLeave(direction: Direction): boolean { 270 switch (this.angle) { 271 case Rotation.UpRight: 272 return [ Direction.Up, Direction.Right ].includes(direction) 273 274 case Rotation.UpLeft: 275 return [ Direction.Up, Direction.Left ].includes(direction) 276 277 case Rotation.DownRight: 278 return [ Direction.Down, Direction.Right ].includes(direction) 279 280 case Rotation.DownLeft: 281 return [ Direction.Down, Direction.Left ].includes(direction) 282 283 case Rotation.Vertical: 284 return [ Direction.Right, Direction.Left ].includes(direction) 285 286 case Rotation.Horizontal: 287 return [ Direction.Up, Direction.Down ].includes(direction) 288 289 case Rotation.Up: 290 return direction === Direction.Up 291 292 case Rotation.Right: 293 return direction === Direction.Right 294 295 case Rotation.Down: 296 return direction === Direction.Down 297 298 case Rotation.Left: 299 return direction === Direction.Left 300 } 301 } 302 } 303 304 // Balise de début de niveau 305 export class Start extends Cell { 306 public constructor(position: Point) { 307 super(position) 308 } 309 310 public render(_ctx: CanvasRenderingContext2D): void { 311 // Pas de rendu 312 } 313 } 314 315 // Balise de fin de niveau 316 export class End extends Cell { 317 private active: boolean 318 319 public constructor(position: Point) { 320 super(position) 321 322 this.active = false 323 324 this.getAnimationManager().addAnimation('inactive', [ 20 ]) 325 326 this.getAnimationManager().addAnimation('active', [ 21, 22, 23, 24, 23, 22 ], { 327 frameDuration: 0.1, 328 }) 329 330 this.getAnimationManager().play('inactive') 331 } 332 333 public onAfterPlayerIn(player: Player, game: Game): this | null { 334 if (this.isActive()) { 335 player.setImmobility(true) 336 player.getAnimationManager().play('turn') 337 338 setTimeout(() => { 339 game.nextLevel() 340 }, 480) 341 } 342 343 return this 344 } 345 346 public activate(): void { 347 this.active = true 348 349 this.getAnimationManager().play('active') 350 } 351 352 public isActive(): boolean { 353 return this.active 354 } 355 } 356 357 // Pièce 358 export class Coin extends Cell { 359 public constructor(position: Point) { 360 super(position) 361 362 this.getAnimationManager().addAnimation('idle', [ 10 ]) 363 364 this.getAnimationManager().play('idle') 365 } 366 367 public onAfterPlayerIn(_player: Player, _game: Game): this | null { 368 return null 369 } 370 } 371 372 // Glace 373 export class Ice extends Cell { 374 public constructor(position: Point) { 375 super(position) 376 377 this.getAnimationManager().addAnimation('idle', [ 2 ]) 378 379 this.getAnimationManager().play('idle') 380 } 381 382 public onAfterPlayerIn(player: Player, _game: Game): this | null { 383 player.move(player.getDirection(), 'idle') 384 385 return this 386 } 387 } 388 389 // Élévation de terrain / Motte de terre 390 export class Elevation extends Cell { 391 public constructor(position: Point) { 392 super(position) 393 394 this.getAnimationManager().addAnimation('idle', [ 1 ]) 395 396 this.getAnimationManager().play('idle') 397 } 398 399 public onBeforePlayerIn(player: Player): void { 400 player.getAnimationManager().play(`jump-${Direction.Down.toString()}`, true) 401 } 402 403 public onAfterPlayerIn(player: Player, _game: Game): this | null { 404 player.move(player.getDirection(), null) 405 406 return this 407 } 408 409 public canEnter(direction: Direction): boolean { 410 if (direction === Direction.Down) { 411 return false 412 } 413 414 return true 415 } 416 } 417 418 export const cells: { [key: string]: (position: Point) => Cell } = { 419 '#': (position: Point): Cell => new Stone(position), 420 421 'S': (position: Point): Cell => new Start(position), 422 'E': (position: Point): Cell => new End(position), 423 424 '$': (position: Point): Cell => new Coin(position), 425 426 '^': (position: Point): Cell => new Conveyor(position, Direction.Up), 427 'v': (position: Point): Cell => new Conveyor(position, Direction.Down), 428 '>': (position: Point): Cell => new Conveyor(position, Direction.Right), 429 '<': (position: Point): Cell => new Conveyor(position, Direction.Left), 430 431 'T': (position: Point): Cell => new Turnstile(position, Rotation.UpRight), 432 'F': (position: Point): Cell => new Turnstile(position, Rotation.UpLeft), 433 'J': (position: Point): Cell => new Turnstile(position, Rotation.DownRight), 434 'L': (position: Point): Cell => new Turnstile(position, Rotation.DownLeft), 435 436 '=': (position: Point): Cell => new Turnstile(position, Rotation.Horizontal), 437 'H': (position: Point): Cell => new Turnstile(position, Rotation.Vertical), 438 439 '8': (position: Point): Cell => new Turnstile(position, Rotation.Up), 440 '6': (position: Point): Cell => new Turnstile(position, Rotation.Right), 441 '2': (position: Point): Cell => new Turnstile(position, Rotation.Down), 442 '4': (position: Point): Cell => new Turnstile(position, Rotation.Left), 443 444 'B': (position: Point): Cell => new Button(position, 1), 445 'B2': (position: Point): Cell => new Button(position, 2), 446 'B3': (position: Point): Cell => new Button(position, 3), 447 448 '!': (position: Point): Cell => new Ice(position), 449 'U': (position: Point): Cell => new Elevation(position), 450 }