diff --git a/css/style.css b/css/style.css index 4192d15..4e56d59 100644 --- a/css/style.css +++ b/css/style.css @@ -8,17 +8,66 @@ left: 0; height: 100%; width: 100%; + border-radius: 10px; } .bg { z-index: -1; background-color: rgb(226, 233, 175); } - -#container { +.game { position: relative; height: 80vh; background-size: 100% 100%; aspect-ratio: 9 / 18; + z-index: 99; + transform: scale(0.9); + border-radius: 10px; + border: 1px solid black; +} +.container{ + position: relative; margin: auto; + height: min-content; + width: 400px; + background-color: rgb(158,173,134); + /* z-index: ; */ +} +button,.score{ + padding: 8px 16px; + color: white; + border: none; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + cursor: pointer; + transition: background-color 0.3s; +} +.start{ + position: absolute; + top: 50%; + left: 50%; + height: 50px; + width: 100px; + background-color: #328855; + transform: translate(-50%, -50%); + z-index: 99; +} +.start:hover{ + background-color: #64c88a; +} +.pause{ + display: none; + position: absolute; + background-color: brown; + top: 50%; + right: 0; +} +.score{ + position: absolute; + background-color: brown; + top: 20%; + right: 0; +} +.restart{ + display: none; } diff --git a/index.html b/index.html index 301adab..836d1ac 100644 --- a/index.html +++ b/index.html @@ -8,10 +8,15 @@ -
0
-
- - +
+
得分:
0
+ +
+ + + + +
diff --git a/src/brick/brickConfig.ts b/src/brick/brickConfig.ts index a331b75..58ca262 100644 --- a/src/brick/brickConfig.ts +++ b/src/brick/brickConfig.ts @@ -1,4 +1,4 @@ -import { Bricks } from "../types"; +import { Bricks } from "../types/brick"; // prettier-ignore export const bricks:Bricks = { diff --git a/src/brick/index.ts b/src/brick/index.ts index e049505..a3d35f2 100644 --- a/src/brick/index.ts +++ b/src/brick/index.ts @@ -1,6 +1,6 @@ import { gameParam } from "../gameConfig" import { drawBrick } from "../draw" -import { BinaryString, BrickLetter, BrickStruct, IBrick } from "../types" +import { BinaryString, BrickLetter, BrickStruct, IBrick } from "../types/brick" import { bricks } from "./brickConfig" const getRandomLetter = (): BrickLetter => { diff --git a/src/canvasWithMapCtx.ts b/src/canvasWithMapCtx.ts index 4c76099..10f420f 100644 --- a/src/canvasWithMapCtx.ts +++ b/src/canvasWithMapCtx.ts @@ -1,5 +1,6 @@ import { gameParam } from "./gameConfig" -import { BrickColor, ICanvasWithMapCtx } from "./types" +import { ICanvasWithMapCtx } from "./types" +import { BrickColor } from "./types/brick" import { $ } from "./utils" const canvas = $(".canvas.brick") as HTMLCanvasElement diff --git a/src/draw/brickStyle.ts b/src/draw/brickStyle.ts index 7cf7088..0f4ce6f 100644 --- a/src/draw/brickStyle.ts +++ b/src/draw/brickStyle.ts @@ -1,5 +1,5 @@ import { gameParam } from "../gameConfig" -import { IBrick } from "../types" +import { IBrick } from "../types/brick" export const drawStyle = ( ctx: CanvasRenderingContext2D, diff --git a/src/draw/drawBrickPiece.ts b/src/draw/drawBrickPiece.ts index 54f08e1..183e27b 100644 --- a/src/draw/drawBrickPiece.ts +++ b/src/draw/drawBrickPiece.ts @@ -1,5 +1,5 @@ import { gameParam } from "../gameConfig" -import { IBrick } from "../types" +import { IBrick } from "../types/brick" import { drawStyle } from "./brickStyle" const offsetCanvas = document.createElement("canvas") diff --git a/src/draw/index.ts b/src/draw/index.ts index 209cff9..080bed4 100644 --- a/src/draw/index.ts +++ b/src/draw/index.ts @@ -1,5 +1,5 @@ import { gameParam } from "../gameConfig" -import { BrickColor, IBrick } from "../types" +import { BrickColor, IBrick } from "../types/brick" import { drawBrickPiece } from "./drawBrickPiece" export const drawBrick = ( diff --git a/src/gameConfig.ts b/src/gameConfig.ts index 6f07070..ee60399 100644 --- a/src/gameConfig.ts +++ b/src/gameConfig.ts @@ -1,13 +1,13 @@ import { GameParam } from "./types" import { $ } from "./utils" -const container = $("#container") as HTMLDivElement +const container = $(".game") as HTMLDivElement const { width, height } = container.getBoundingClientRect() export const gameParam: GameParam = { column: 10, row: 20, - FPS: 165, + FPS: null, speed: 2, - keySpeed: 8, + keySpeed: 10, score: 0, devicePixelRatio: window.devicePixelRatio, // 给方块计算出整数值宽高,不然小数情况可能会出现方块间的间隙 diff --git a/src/gameHelper.ts b/src/gameHelper.ts index 7cffdbb..4fb8371 100644 --- a/src/gameHelper.ts +++ b/src/gameHelper.ts @@ -1,5 +1,6 @@ import { gameParam } from "./gameConfig" -import { IBrick, ICanvasWithMapCtx } from "./types" +import { ICanvasWithMapCtx } from "./types" +import { IBrick } from "./types/brick" import { SinglePattern } from "./utils" class GameHelper { diff --git a/src/main.ts b/src/main.ts index 670e4ec..6901a2c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,4 @@ -import Game from "./game" -import { $ } from "./utils" +import Ui from "./ui"; -const game = new Game((score) => { - $(".score")!.textContent = score + "" -}) -game.startGame() -console.log(game) + +new Ui() \ No newline at end of file diff --git a/src/operate.ts b/src/operate.ts index a01c6ee..9a4fe10 100644 --- a/src/operate.ts +++ b/src/operate.ts @@ -1,10 +1,10 @@ import { - IBrick, ICanvasWithMapCtx, IRenderer, OperateEvents, PlayWithPause, } from "./types" +import { IBrick } from "./types/brick" export default class Operation implements OperateEvents { constructor( diff --git a/src/renderer.ts b/src/renderer.ts index f3d406f..5e8c025 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -5,20 +5,23 @@ import { userActions } from "./inputHandler" import Operation from "./operate" import Scorer from "./scorer" import { ICanvasWithMapCtx, IRenderer } from "./types" +import { EventEmitter, eventEmitter } from "./ui/eventEmitter" export default class Renderer implements IRenderer { private canvasWithMapCtx: ICanvasWithMapCtx private operation: Operation private scorer: Scorer + private eventEmitter: EventEmitter private gameHelper: GameHelper private brick: Brick - private newBrick: Brick + private nextBrick: Brick constructor(canvasWithMapCtx: ICanvasWithMapCtx) { this.canvasWithMapCtx = canvasWithMapCtx this.scorer = new Scorer() + this.eventEmitter = eventEmitter this.gameHelper = gameHelper this.brick = new Brick() - this.newBrick = new Brick() + this.nextBrick = new Brick() this.operation = new Operation(this, this.canvasWithMapCtx, this.brick, { playGame: this.playGame.bind(this), pauseGame: this.pauseGame.bind(this), @@ -68,12 +71,12 @@ export default class Renderer implements IRenderer { } } private newNextOne(time: number) { - const isSuccess = !this.gameHelper.record( + const isSuccess = this.gameHelper.record( this.canvasWithMapCtx.mapBinary, this.canvasWithMapCtx.bg, this.brick ) - if (isSuccess) { + if (!isSuccess) { this._over = true return } @@ -83,9 +86,10 @@ export default class Renderer implements IRenderer { ) const score = this.gameHelper.computeScore(row) this.scorer.bonusPoint(score) + this.eventEmitter.emit("scoreUpdate", this.scorer.score) drawBg(this.canvasWithMapCtx.bgCtx, this.canvasWithMapCtx.bg) - this.brick = this.newBrick - this.newBrick = new Brick(time) + this.brick = this.nextBrick + this.nextBrick = new Brick(time) this.operation.takeTurns(this.brick) } private cachePauseTime(time: number) { diff --git a/src/types/brick.ts b/src/types/brick.ts new file mode 100644 index 0000000..4f2e0ff --- /dev/null +++ b/src/types/brick.ts @@ -0,0 +1,74 @@ +import { Binary, isBinaryString } from "./helper"; + + +export type BinaryString = { + [K in keyof T]: isBinaryString; +}; +// type a = BinaryString<("00" | "10" | "01" | "11")[]> + +export type Struct< + T extends number, + R extends any[] = [] +> = R["length"] extends T ? R : Struct]>; + +export type BrickLetter = keyof Bricks; + +export type BrickColor = Bricks[BrickLetter]["color"]; + +export type BrickStruct = Bricks[BrickLetter]["struct"]; + + + +export type Bricks = { + [key: string]: { + color: string; + struct: Readonly | Struct<2> | Struct<3> | Struct<4>>; + }; + o: { + color: "#FADADD"; + struct: Readonly>; + }; + i: { + color: "#F7E9D4"; + struct: Readonly>; + }; + s: { + color: "#C8E6C9"; + struct: Readonly>; + }; + z: { + color: "#B3E5FC"; + struct: Readonly>; + }; + l: { + color: "#FFCC80"; + struct: Readonly>; + }; + j: { + color: "#FFEE58"; + struct: Readonly>; + }; + t: { + color: "#CE93D8"; + struct: Readonly>; + }; +}; + +export interface IBrick { + letter: BrickLetter; + x: number; + y: number; + width: number; + height: number; + color: BrickColor; + structure: BinaryString; + isRecycle: boolean; + draw(ctx: CanvasRenderingContext2D): void; + update(time: number, mapBinary: number[]): boolean; + getBinary(): number[]; + left(mapBinary: number[]): void; + right(mapBinary: number[]): void; + downOne(mapBinary: number[]): boolean; + downBottom(mapBinary: number[]): boolean; + rotate(mapBinary: number[]): void; +} diff --git a/src/types/helper.ts b/src/types/helper.ts index e20fbd1..3be59c0 100644 --- a/src/types/helper.ts +++ b/src/types/helper.ts @@ -11,3 +11,7 @@ export type Binary< R extends string = "", Arr extends string[] = [] > = Arr["length"] extends T ? R : Binary + +export type OptionalKeys = { + [K in keyof T]-?: {} extends Pick ? K : never +}[keyof T] \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 46a031a..a80f7c5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,21 +1,5 @@ -import { Binary, isBinaryString } from "./helper" - -export type BinaryString = { - [K in keyof T]: isBinaryString -} - -// type a = BinaryString<("00" | "10" | "01" | "11")[]> - -export type Struct< - T extends number, - R extends any[] = [] -> = R["length"] extends T ? R : Struct]> - -export type BrickLetter = keyof Bricks - -export type BrickColor = Bricks[BrickLetter]["color"] - -export type BrickStruct = Bricks[BrickLetter]["struct"] +import { BrickColor } from "./brick" +import { OptionalKeys } from "./helper" export type GameParam = { readonly windowWidth: number @@ -25,7 +9,7 @@ export type GameParam = { readonly devicePixelRatio: number readonly brickWidth: number readonly brickHeight: number - readonly FPS: number + readonly FPS: number | null speed: number keySpeed: number score: number @@ -40,60 +24,6 @@ export type OperateEvents = { pauseGame: () => void } -export type Bricks = { - [key: string]: { - color: string - struct: Readonly | Struct<2> | Struct<3> | Struct<4>> - } - o: { - color: "#FADADD" - struct: Readonly> - } - i: { - color: "#F7E9D4" - struct: Readonly> - } - s: { - color: "#C8E6C9" - struct: Readonly> - } - z: { - color: "#B3E5FC" - struct: Readonly> - } - l: { - color: "#FFCC80" - struct: Readonly> - } - j: { - color: "#FFEE58" - struct: Readonly> - } - t: { - color: "#CE93D8" - struct: Readonly> - } -} - -export interface IBrick { - letter: BrickLetter - x: number - y: number - width: number - height: number - color: BrickColor - structure: BinaryString - isRecycle: boolean - draw(ctx: CanvasRenderingContext2D): void - update(time: number, mapBinary: number[]): boolean - getBinary(): number[] - left(mapBinary: number[]): void - right(mapBinary: number[]): void - downOne(mapBinary: number[]): boolean - downBottom(mapBinary: number[]): boolean - rotate(mapBinary: number[]): void -} - export interface ICanvasWithMapCtx { ctx: CanvasRenderingContext2D bgCtx: CanvasRenderingContext2D @@ -120,3 +50,16 @@ export interface IGame extends PlayWithPause { restartGame: () => void cancelGame: () => void } + +export interface EmitterEvents { + scoreUpdate?: Function[] + gameOver?: Function[] +} + +export interface IEventEmitter { + events: EmitterEvents + on: (event: OptionalKeys, listener: Function) => void + emit: (event: OptionalKeys) => void + off: (event: OptionalKeys, listener: Function) => void + clearAllListeners: () => void +} diff --git a/src/ui/eventEmitter.ts b/src/ui/eventEmitter.ts new file mode 100644 index 0000000..4edf8e0 --- /dev/null +++ b/src/ui/eventEmitter.ts @@ -0,0 +1,39 @@ +import { EmitterEvents, IEventEmitter } from "../types" +import { OptionalKeys } from "../types/helper" +import { SinglePattern } from "../utils" + +class EventEmitter implements IEventEmitter { + events: EmitterEvents = {} + on(event: OptionalKeys, listener: Function) { + if (!this.events[event]) { + this.events[event] = [] + } + this.events[event]!.push(listener) + } + + emit(event: OptionalKeys, ...args: any[]) { + const listeners = this.events[event] + if (listeners) { + listeners.forEach((listener) => { + listener(...args) + }) + } + } + + off(event: OptionalKeys, listener: Function) { + const listeners = this.events[event] + if (listeners) { + this.events[event] = listeners.filter((l) => l !== listener) + } + } + + clearAllListeners() { + this.events = {} + } +} + +const SingleEventEmitter = SinglePattern(EventEmitter) +const eventEmitter = new SingleEventEmitter() + +export { eventEmitter } +export type { EventEmitter } diff --git a/src/ui/index.ts b/src/ui/index.ts new file mode 100644 index 0000000..19c7e4d --- /dev/null +++ b/src/ui/index.ts @@ -0,0 +1,44 @@ +import Game from "../game" +import { $ } from "../utils" +import { EventEmitter, eventEmitter } from "./eventEmitter" + +export default class Ui { + game: Game + dom: Record + eventEmitter: EventEmitter + constructor() { + this.game = new Game() + this.eventEmitter = eventEmitter + this.dom = { + brickCanvas: $(".brick")! as HTMLCanvasElement, + bgCanvas: $(".bg")! as HTMLCanvasElement, + start: $(".start")! as HTMLElement, // Cast the result to HTMLElement + pause: $(".pause")! as HTMLElement, + restart: $(".restart")! as HTMLElement, + score: $(".score>span")! as HTMLElement, + } + this.init() + this.addEvent() + } + init() { + this.eventEmitter.on("scoreUpdate", (score: number) => { + this.dom.score.innerText = score + "" + }) + } + addEvent() { + this.dom.start.addEventListener("click", () => { + this.game.startGame() + this.dom.start.style.display = "none" + this.dom.pause.style.display = "block" + }) + this.dom.pause.addEventListener("click", () => { + if (this.game.pause) { + this.game.playGame() + this.dom.pause.innerText = "暂停" + } else { + this.game.pauseGame() + this.dom.pause.innerText = "继续" + } + }) + } +} diff --git a/src/utils.ts b/src/utils.ts index d761965..3beafbc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ const $ = (selector: string) => { const customRaf = ( fn: (time: number, ...args: any[]) => unknown, - fps?: number + fps?: number | null ) => { if (!!fps) { if (!Number.isInteger(fps) || fps <= 0) { @@ -68,5 +68,4 @@ function SinglePattern any>(Ctor: T) { return p } - export { $, customRaf, throttle, SinglePattern }