import { Ball } from '../objects/ball' ; import { Block } from '../objects/block' ; import { Game } from './game' ; import { Direction , intersectDirection } from '../utils/intersect' ; import { Player } from '../objects/player' ; import { createButton , createDivElement , createShape } from '../utils/domUtils' ; import { getHighScore , saveScore } from '../utils/score' ; enum State { Ready , Playing , Done } export class Breakout extends Game { state : State = State . Ready ; score = 0 ; ball ! : Ball ; blocks ! : Block [ ] ; player ! : Player ; svgElement : SVGElement ; headerElement ! : HTMLElement | null ; button ! : HTMLButtonElement ; footerElement ! : HTMLDivElement ; scoreElement ! : SVGElement ; constructor ( svgElement ) { super ( ) ; this . svgElement = svgElement ; ( async ( ) => { this . initGameObject ( ) ; await this . initUI ( ) ; this . startGameLoop ( ) ; } ) ( ) ; } initGameObject ( ) { this . ball = new Ball ( this . svgElement ) ; this . player = new Player ( this . svgElement ) ; this . blocks = [ ... this . svgElement . querySelectorAll ( 'rect' ) ] . filter ( ( e ) => e . getAttribute ( 'data-count' ) !== '0' ) . map ( ( e ) => new Block ( this . svgElement , e ) ) ; } async initUI ( ) { this . scoreElement = this . svgElement . appendChild ( createShape ( 'text' , { x : 20 , y : 130 , fill : 'var(--color-text-primary)' } , '' ) ) ; this . headerElement = document . querySelector < HTMLElement > ( '.js-yearly-contributions h2' ) ; const uiContainer = document . querySelector ( '.js-calendar-graph' ) ; if ( ! uiContainer ) return ; this . button = uiContainer . appendChild ( createButton ( this . blocks . length > 0 ? `Play!` : '🥺' , ( ) => this . onButtonClick ( ) ) ) ; const hs = await getHighScore ( ) ; this . footerElement = uiContainer . appendChild ( createDivElement ( hs > 0 ? `HighScore: ${ hs }` : 'Press the arrow keys to play ←→' ) ) ; } param delta update ( delta ) { if ( this . state !== State . Playing ) return ; this . ball . update ( delta ) ; this . player . update ( delta ) ; this . blocks . forEach ( ( b ) => b . update ( delta ) ) ; let remainingContributions = 0 ; let collideFlagX = false ; let collideFlagY = false ; this . blocks . filter ( ( b ) => b . life > 0 ) . forEach ( ( b ) => { const d = intersectDirection ( this . ball , b ) ; if ( d !== Direction . None ) { b . onCollide ( ) ; this . score += b . origianlLife ; collideFlagX ||= d == Direction . X || d == Direction . XY ; collideFlagY ||= d == Direction . Y || d == Direction . XY ; } remainingContributions += b . life ; } ) ; if ( collideFlagX && collideFlagY ) this . ball . onCollide ( Direction . XY ) ; else if ( collideFlagX ) this . ball . onCollide ( Direction . X ) ; else if ( collideFlagY ) this . ball . onCollide ( Direction . Y ) ; this . ball . onCollide ( intersectDirection ( this . ball , this . player ) ) ; this . updateLabel ( remainingContributions ) ; if ( this . ball . y > 220 ) { this . state = State . Done ; this . button . textContent = 'GameOver!' ; saveScore ( this . score ) ; } if ( remainingContributions === 0 ) { this . state = State . Done ; this . button . textContent = 'Clear!' ; saveScore ( this . score ) ; } } param contributons updateLabel ( contributons ) { const tmp = this . headerElement ?. textContent ?. match ( /.*?[0-9,]+([\s\S]*)/m ) ; if ( this . headerElement && tmp ) this . headerElement . textContent = `${ contributons . toLocaleString ( ) }${ tmp [ 1 ] . replace ( /\n/ , '' ) }` ; this . scoreElement . textContent = `score: ${ this . score }` ; } /**
     * footer button
     */ onButtonClick ( ) { if ( this . blocks . length === 0 ) { location . href = 'https://github.com/new' ; return ; } switch ( this . state ) { case State . Ready : this . state = State . Playing ; this . button . textContent = 'Reset' ; break ; case State . Playing : this . reset ( ) ; break ; case State . Done : this . reset ( ) ; break ; } } /**
     * Reset all status
     */ async reset ( ) { this . state = State . Ready ; let life = 0 ; this . blocks . forEach ( ( b ) => { b . reset ( ) ; life += b . origianlLife ; } ) ; this . player . reset ( ) ; this . ball . reset ( ) ; this . score = 0 ; this . button . textContent = 'Play!' ; this . scoreElement . textContent = '' ; this . footerElement . textContent = `HighScore: ${ await getHighScore ( ) }` ; this . updateLabel ( life ) ; } }