/ src / Cell.ts
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  }