From e48af07ef9b45dd2a97efc36e5f380c30859e607 Mon Sep 17 00:00:00 2001 From: Nicolas Ramz Date: Tue, 2 Oct 2018 06:24:20 +0200 Subject: [PATCH] Added definitions for the athenajs npm package (#29326) * ADDED: initial files, more work needed * FIXED: more classes * FIXED: some fixes to incorrect types so that gods has no errors * IMPROVED: more strick tsconfig FIXED: tslint.json typo in filename * FIXED: npm test errors ADDED: UMD global var AthenaJS --- types/athenajs/index.d.ts | 784 +++++++++++++++++++ types/athenajs/test/bitmaptext.ts | 18 + types/athenajs/test/deferred.ts | 28 + types/athenajs/test/dom.ts | 33 + types/athenajs/test/drawable.ts | 36 + types/athenajs/test/game.ts | 23 + types/athenajs/test/map.ts | 29 + types/athenajs/test/paint.ts | 12 + types/athenajs/test/scene.ts | 58 ++ types/athenajs/test/simpletext.ts | 17 + types/athenajs/test/sprite.ts | 28 + types/athenajs/test/tetris/flash_lines.ts | 35 + types/athenajs/test/tetris/grid.ts | 469 +++++++++++ types/athenajs/test/tetris/shape.ts | 256 ++++++ types/athenajs/test/tetris/shape_behavior.ts | 140 ++++ types/athenajs/test/tetris/tetris.ts | 11 + types/athenajs/tsconfig.json | 40 + types/athenajs/tslint.json | 3 + 18 files changed, 2020 insertions(+) create mode 100644 types/athenajs/index.d.ts create mode 100644 types/athenajs/test/bitmaptext.ts create mode 100644 types/athenajs/test/deferred.ts create mode 100644 types/athenajs/test/dom.ts create mode 100644 types/athenajs/test/drawable.ts create mode 100644 types/athenajs/test/game.ts create mode 100644 types/athenajs/test/map.ts create mode 100644 types/athenajs/test/paint.ts create mode 100644 types/athenajs/test/scene.ts create mode 100644 types/athenajs/test/simpletext.ts create mode 100644 types/athenajs/test/sprite.ts create mode 100644 types/athenajs/test/tetris/flash_lines.ts create mode 100644 types/athenajs/test/tetris/grid.ts create mode 100644 types/athenajs/test/tetris/shape.ts create mode 100644 types/athenajs/test/tetris/shape_behavior.ts create mode 100644 types/athenajs/test/tetris/tetris.ts create mode 100644 types/athenajs/tsconfig.json create mode 100644 types/athenajs/tslint.json diff --git a/types/athenajs/index.d.ts b/types/athenajs/index.d.ts new file mode 100644 index 0000000000..08c828679e --- /dev/null +++ b/types/athenajs/index.d.ts @@ -0,0 +1,784 @@ +// Type definitions for athenajs 0.1 +// Project: https://github.com/AthenaJS/athenajs +// Definitions by: Nicolas Ramz +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +export as namespace AthenaJS; + +export function Dom(sel?: string | HTMLElement): _Dom; + +export class Scene { + constructor(options?: SceneOptions); + map: Map; + hudScene: Scene | null; + running: boolean; + opacity: number; + addObject(object: Drawable | Drawable[], layer?: number): Scene; + animate(fxName: string, options: EffectOptions): Promise; + bindEvents(eventList: string): void; + debug(bool?: boolean): void; + fadeIn(duration: number): Promise; + fadeOut(duration: number): Promise; + fadeInAndOut(inDuration: number, delay: number, outDuration: number): Promise; + getOpacity(): number; + getPlayTime(): number; + load(type: string, src: string, id?: string): void; + loadAudio(src: string, id?: string): void; + loadImage(src: string, id?: string): void; + loadMap(src: string, id?: string): void; + notify(name: string, data?: JSObject): void; + removeObject(obj: Drawable): void; + setBackgroundImage(image: string|HTMLImageElement): void; + setLayerPriority(layer: number, background: boolean): void; + setMap(map: Map | JSObject, x?: number, y?: number): void; + setOpacity(opacity: number): void; + setup(): void; + start(): void; + stop(): void; +} +export class Game { + constructor(options: GameOptions); + bindEvents(eventList: string): void; + setScene(scene: Scene): void; + toggleFullscreen(): void; + toggleSound(bool: boolean): void; + toggleTileInspector(bool: boolean): void; + togglePause(): void; + scene: Scene; + sound: boolean; +} +export class Drawable { + constructor(type: string, options: DrawableOptions); + addChild(child: Drawable): void; + animate(name: string, options: JSObject): Promise; + center(): Drawable; + destroy(data?: any): void; + moveTo(x: number, y: number, duration?: number): Drawable; + notify(id: string, data?: JSObject): void; + onCollision(object: Drawable): void; + onEvent(eventType: string, data?: JSObject): void; + playSound(id: string, options?: { pan?: boolean, loop?: false }): void; + setBehavior(behavior: string | { new(sprite: Drawable, options?: JSObject): Behavior }, options?: JSObject): void; + setScale(scale: number): void; + getCurrentWidth(): number; + getCurrentHeight(): number; + getProperty(prop: string): any; + setProperty(prop: string, value: any): void; + setMask(mask: MaskOptions | null, exclude?: boolean): void; + stopAnimate(endValue?: number): void; + reset(): void; + show(): void; + hide(): void; + type: string; + width: number; + height: number; + x: number; + y: number; + vx: number; + vy: number; + canCollide: boolean; + currentMovement: string; + running: boolean; + movable: boolean; + behavior: Behavior; + currentMap: Map; + data: JSObject; + visible: boolean; +} + +export interface MaskOptions { + x: number; + y: number; + width: number; + height: number; +} + +export interface MenuItem { + text: string; + selectable: boolean; + visible: boolean; + active?: boolean; +} + +export interface MenuOptions { + title: string; + color: string; + menuItems: MenuItem[]; +} + +export class Menu extends Drawable { + constructor(id: string, options: MenuOptions); + nextItem(): void; + getSelectedItemIndex(): number; +} + +export class SimpleText extends Drawable { + constructor(type: string, simpleTextOptions: SimpleTextOptions); + getCurrentOffsetX(): number; + getCurrentOffsetY(): number; + setColor(color: string): void; + setSize(width: number, height: number): void; + setText(text: string): void; +} +export class Paint extends Drawable { + constructor(type: string, paintOptions: PaintOptions); + arc(cx: number, cy: number, r: number, starteAngle: number, endAngle: number, fillStyle: string, borderSize: number): void; + fill(color?: string): void; + circle(cx: number, cy: number, r: number, fillStyle?: string, borderWidth?: number, borderStyle?: string): void; + rect(x: number, y: number, width: number, height: number, color: string): void; + name: string; + color: string; +} + +export class BitmapText extends Drawable { + constructor(type: string, textOptions: BitmapTextOptions); + setText(text: string): void; +} + +export class Sprite extends Drawable { + constructor(type: string, spriteOptions: SpriteOptions); + addAnimation(name: string, imgPath: string, options: AnimOptions): void; + setAnimation(name: string, fn?: Callback, frameNum?: number, revert?: boolean): void; + clearMove(): void; +} + +export interface pixelPos { + x: number; + y: number; +} + +export class Map { + constructor(options: MapOptions); + addObject(obj: Drawable, layerIndex?: number): void; + addTileSet(tiles: TileDesc[]): void; + checkMatrixForCollision(buffer: number[], matrixWidth: number, x: number, y: number, behavior: number): boolean; + clear(tileNum?: number, behavior?: number): void; + getTileBehaviorAtIndex(col: number, row: number): number; + getTileIndexFromPixel(x: number, y: number): pixelPos; + moveTo(x: number, y: number): void; + respawn(): void; + setData(map: Uint8Array, behaviors: Uint8Array): void; + setEasing(easing: string): void; + shift(startLine: number, height: number): void; + updateTile(col: number, row: number, tileNum?: number, behavior?: number): void; + duration: number; + numRows: number; + numCols: number; + width: number; + height: number; + tileWidth: number; + tileHeight: number; +} + +export class Tile { + constructor(options: JSObject); + static TYPE: { + AIR: 1; + WALL: 2; + LADDER: 3; + }; + offsetX: number; + offsetY: number; + width: number; + height: number; + + inertia: number; + upCollide: boolean; + downCollide: boolean; +} + +export interface TileDesc { + offsetX: number; + offsetY: number; + width: number; + height: number; +} + +export interface MapOptions { + src: string; + tileWidth: number; + tileHeight: number; + width: number; + height: number; + viewportW?: number; + viewportH?: number; + buffer?: ArrayBuffer; +} + +export interface FXInstance { + addFX(fxName: string, FxClass: { new(options: EffectOptions, display: Display): Effect }): void; +} + +export const FX: FXInstance; + +export class _FX { + /** + * Creates the FX class, adding the linear easing + */ + constructor(); + + /** + * Add a new Effect + */ + addFX(fxName: string, FxClass: { new(): Effect }): void; + + /** + * Retrieve an effect Class by its name + * + */ + getEffect(fxName: string): Effect; + + /** + * Add a new easing function for other objects to use + * + */ + addEasing(easingName: string, easingFn: (x?: number, t?: number, b?: number, c?: number, d?: number) => void): void; + + /** + * Retrieves an easing function + * + */ + getEasing(easingName: string): (x?: number, t?: number, b?: number, c?: number, d?: number) => void; +} + +export interface EffectOptions { + easing?: string; + when?: string; + startValue?: number; + endValue?: number; + duration?: number; +} + +export class Effect { + width: number; + height: number; + buffer: RenderingContext; + animProgress: number; + startValue: number; + ended: boolean; + /** + * This the class constructor. Default options are: + * + */ + constructor(options: EffectOptions, display: Display); + + /** + * Changes the easing function used for the ffect + * + */ + setEasing(easing: (x?: number, t?: number, b?: number, c?: number, d?: number) => void): void; + + /** + * Called when the ffect is started. + * + * This method can be overriden but the super should always be called + */ + start(): Promise; + + /** + * called when the effect is stopped + */ + stop(object: any, setEndValue: any): void; + + /** + * Calculates current animation process + * + * This method can be overridden but the super should always be calle first + */ + process(ctx: RenderingContext, fxCtx?: RenderingContext, obj?: any): boolean; +} + +// why do we need this ? +export type RenderingContext = CanvasRenderingContext2D; + +export interface DisplayOptions { + width: number; + height: number; + type: string; + layers?: boolean[]; + name: string; +} + +export class Display { + /** + * Creates a new Display instance + * + */ + constructor(options: DisplayOptions, target: string | HTMLElement); + + /** + * Creates a new (offscreen) drawing buffer + * + */ + getBuffer(width: number, height: number): RenderingContext; + + /** + * Toggles fullscreen display scaling + */ + toggleFullscreen(): void; + + /** + * Changes the zIndex property of the specified layer canvas + * + */ + setLayerZIndex(layer: number, zIndex: number): void; + + /** + * Clears a canvas display buffer + * + */ + clearScreen(ctx: RenderingContext): void; + + /** + * Clears every rendering buffer, including the special fxCtx one + */ + clearAllScreens(): void; + + /** + * Changes the (CSS) opacity of a canvas + * + */ + setCanvasOpacity(canvas: HTMLElement, opacity: number): void; + + /** + * Renders the specified scene + * + */ + renderScene(scene: Scene): void; + + /** + * Prepares the canvas before rendering images. + * + * Explanation: during development, I noticed that the very first time + * the ctx.drawImage() was used to draw onto a canvas, it took a very long time, + * like at least 10ms for a very small 32x32 pixels drawImage. + * + * Subsequent calls do not have this problem and are instant. + * Maybe some ColorFormat conversion happens. + * + * This method makes sure that when the game starts rendering, we don't have + * any of these delays that can impact gameplay and alter the gameplay experience + * in a negative way. + */ + prepareCanvas(resources: JSObject[]): void; + + /** + * Starts an animation on the display + * + */ + animate(fxName: string, options: EffectOptions, context: RenderingContext): Promise; + + /** + * stops current animation + * + * TODO + */ + stopAnimate(fxName?: string): void; + + /** + * Executes an effect on a frame at a given time + * + */ + executeFx(ctx: RenderingContext, fxCtx: RenderingContext, obj: Drawable, time: number, when: string): void; + + /** + * Clears every display layer and clears fx queues + */ + clearDisplay(): void; +} + +export const InputManager: _InputManager; + +export class MapEvent { + /** + * Creates a new MapEvent + * + */ + constructor(map: Map); + + /** + * Resets the MapEvent switches, events and items + */ + reset(): void; + + /** + * Adds a new [`Drawable`]{#item} onto the map + * + */ + addItem(id: string, item: Drawable): void; + + /** + * Returns an item + * + */ + getItem(id: string): Drawable | undefined; + + // TODO: ability to trigger an event once a switch has been modified + setSwitch(id: string, bool: boolean): void; + + toggleSwitch(id: string): void; + + /** + * Retrieves a switch from the map using its id + * + */ + getSwitch(id: string): any; + + /** + * checks of conditions of specified trigger are valid + * + */ + checkConditions(trigger: JSObject): boolean; + + handleAction(options: JSObject): void; + + handleEvent(options: JSObject): boolean; + + /** + * Schedule adding a new object to the map + */ + scheduleSprite(spriteId: string, spriteOptions: JSObject, delay: number): Drawable; + + /** + * Add a new wave of objects to the map + * Used for example when the player triggers apparition of several enemies or bonuses + * + * @related {Wave} + */ + handleWave(options: JSObject): boolean; + + endWave(): void; + + triggerEvent(id: string): void; + + isEventTriggered(id: string): boolean; +} + +export class Behavior { + vx: number; + vy: number; + gravity: number; + sprite: Drawable; + constructor(sprite: Drawable, options?: JSObject); + onUpdate(timestamp: number): void; + onVXChange?(vx: number): void; + onVYChange?(vy: number): void; + + /** + * Returns current mapEvent + * + */ + getMapEvent(): MapEvent; + reset(): void; +} + +export interface _AudioManager { + audioCache: JSObject; + enabled: boolean; + /** + * Adds a new sound element to the audio cache. + * *Note* if a sound with the same id has already been added, it will be replaced + * by the new one. + * + */ + addSound(id: string, element: HTMLAudioElement): void; +/** + * Toggles global sound playback + * + */ + toggleSound(bool: boolean): void; +/** + * Plays the specified sound with `id`. + * + */ + play(id: string, loop?: boolean, volume?: number, panning?: number): any; +/** + * Stops playing the sound id + * + */ + stop(id: string, instanceId: any): void; +} + +export const AudioManager: _AudioManager; + +export interface Res { + id: string; + type: string; + src: string; +} + +export type Callback = (...args: any[]) => void; + +export interface _NotificationManager { + notify(name: string, data?: JSObject): void; +} + +export const NotificationManager: _NotificationManager; + +export interface _ResourceManager { + addResources(resource: Res, group?: string): Promise; + getCanvasFromImage(image: HTMLImageElement): HTMLCanvasElement; + getResourceById(id: string, group?: string, fullObject?: boolean): any; + loadResources(group: string, progressCb?: Callback, errorCb?: Callback): void; + loadImage(res: Res, group?: string): Promise; + loadAudio(res: Res, group?: string): Promise; + newResourceFromPool(id: string): any; + registerScript(id: string, elt: any, poolSize?: number): void; +} + +export const ResourceManager: _ResourceManager; + +export interface _InputManager { +/** + * A list of common keyCodes + */ +KEYS: { + 'UP': 38, + 'DOWN': 40, + 'LEFT': 37, + 'RIGHT': 39, + 'SPACE': 32, + 'ENTER': 13, + 'ESCAPE': 27, + 'CTRL': 17 +}; +/** + * List of common pad buttons + */ +PAD_BUTTONS: { + 32: 1, // Face (main) buttons + FACE_0: 1, + FACE_3: 2, + FACE_4: 3, + LEFT_SHOULDER: 4, // Top shoulder buttons + RIGHT_SHOULDER: 5, + LEFT_SHOULDER_BOTTOM: 6, // Bottom shoulder buttons + RIGHT_SHOULDER_BOTTOM: 7, + SELECT: 8, + START: 9, + LEFT_ANALOGUE_STICK: 10, // Analogue sticks (if depressible) + RIGHT_ANALOGUE_STICK: 11, + 38: 12, // Directional (discrete) pad + 40: 13, + 37: 14, + 39: 15 +}; +axes: JSObject; +newGamepadPollDelay: number; +gamepadSupport: boolean; +recording: boolean; +playingEvents: boolean; +playingPos: number; +/*recordedEvents: Array,*/ +pad: null; +latches: JSObject; +keyPressed: JSObject; +padPressed: JSObject; +keyCb: JSObject; +enabled: boolean; +inputMode: string; +// virtual joystick instance +dPadJoystick: null; +jPollInterval: number; +/** + * Initializes the InputManager with a reference to the game. + * + * This method prepares the InputManager by reseting keyboard states/handlers and + * set current inputMode + * + */ + init(): void; +/** + * Starts recording input events. They are stored into `InputManager.recordedEvents` + */ +startRecordingEvents(): void; +/** + * Stops recording events. + */ +stopRecordingEvents(): void; +/** + * After events have been reccorded they can be played back using this method. + */ +playRecordedEvents(): void; +/** + * Sets next key states using recorded events + * + * TODO: add an optional callback to be called at the end of the playback + * so that demo can be looped. + */ +nextRecordedEvents(): void; +/** + * Saves current event state onto the recordedEvents stack + */ +/** + * Changes input mode + * + */ +setInputMode(mode: string): void; + /** + * Returns an object with the state of all keys + */ + getAllKeysStatus(): JSObject; + getKeyStatus(key: string, latch: boolean): boolean; + isKeyDown(key: string|number, latch?: boolean): boolean; + /** + * Install callback that gets called when a key is pressed/released + * + */ + installKeyCallback(key: string, event: string, callback: (key: string, event: string) => void): void; + removeKeyCallback(key: string, event: string, callback: () => void): void; + clearEvents(): void; +} + +export interface Promise { + then(val?: () => any): Promise; + catch(val?: () => any): Promise; +} + +/* Deferred */ +export class Deferred { + constructor(); + /** + * Creates and immediately resolves a new deferred. + * + */ + static resolve(val?: any): Promise; + promise: Promise; + reject(val: any): void; + resolve(val: any): void; +} + +/* Dom support */ +export interface _Dom extends Iterable { + [key: number]: TElement; + length: number; + css(prop: string, val: string): _Dom; + css(prop: JSObject): _Dom; + css(prop: string): string|null; + find(selector: string): _Dom; + appendTo(selector: string | _Dom | HTMLElement): _Dom; + attr(att: string, val: string): _Dom; + attr(att: JSObject): _Dom; + addClass(classes: string): _Dom; + removeClass(classes: string): _Dom; + html(str: string): _Dom; + show(): _Dom; + hide(): _Dom; +} + +/* Game Support */ +export interface GameOptions { + name: string; + showFps: boolean; + width: number; + height: number; + debug: boolean; + scene?: Scene; + target?: string | HTMLElement; + sound?: boolean; +} + +export interface SceneOptions { + name?: string; + resources?: Res[]; + opacity?: number; + layers?: number; + hudScene?: Scene; +} + +export interface DrawableOptions { + x?: number; + y?: number; + behavior?: { new(sprite: Drawable, options?: JSObject): Behavior }; + canCollide?: boolean; + canCollideFriendBullet?: boolean; + collideGroup?: number; + objectId?: string; + layer?: number; + map?: Map; + visible?: boolean; + pool?: number; +} + +export interface SimpleTextOptions extends DrawableOptions { + text?: string; + width?: number; + height?: number; + fontFace?: string; + fontSize?: string; + fontStyle?: string; + fontWeight?: string; + align?: string; + color?: string; +} + +export interface PaintOptions extends DrawableOptions { + width?: number; + height?: number; + color?: string; + lineHeight?: number; +} + +export interface BitmapTextOptions extends DrawableOptions { + width?: number; + height?: number; + offsetX: number; + startY: number; + charWidth: number; + charHeight: number; + imageId?: string; + imageSrc?: string; + scrollOffsetX?: number; + scrollOffsetY?: number; + text?: string; + size?: string; +} + +export interface SpriteOptions extends DrawableOptions { + easing?: string; + imageId?: string; + animations?: Animations; + data?: JSObject; +} + +export interface AnimOptions { + numFrames: number; + frameWidth: number; + frameHeight: number; + frameDuration: number; + offsetX?: number; + offsetY?: number; + frameSpacing?: number; +} + +export interface AnimationObject { + frameDuration?: number; + frames: Array<{ + offsetX: number; + offsetY: number; + width: number; + height: number; + hitBox?: { + x: number; + y: number; + x2: number; + y2: number; + }, + plane?: number; + }>; + loop?: number; + speed?: number; +} + +export interface JSObject { + [key: string]: any; +} + +export interface Animations { + [key: string]: AnimationObject; +} + +export interface GameEvent { + type: string; + data: JSObject; +} diff --git a/types/athenajs/test/bitmaptext.ts b/types/athenajs/test/bitmaptext.ts new file mode 100644 index 0000000000..b32a0d2541 --- /dev/null +++ b/types/athenajs/test/bitmaptext.ts @@ -0,0 +1,18 @@ +import { BitmapText } from 'athenajs'; + +const bitmap: BitmapText = new BitmapText('myBitmap', { + size: 'big', + width: 180, + height: 32, + visible: false, + scrollOffsetX: 0, + scrollOffsetY: 0, + text: 'pause', + offsetX: 34, + startY: 36, + charWidth: 18, + charHeight: 18, + imageId: 'font' +}); + +bitmap.center(); diff --git a/types/athenajs/test/deferred.ts b/types/athenajs/test/deferred.ts new file mode 100644 index 0000000000..5cdc077a19 --- /dev/null +++ b/types/athenajs/test/deferred.ts @@ -0,0 +1,28 @@ +import { Deferred } from 'athenajs'; + +let def: Deferred; + +// static Deferred.resolve +Deferred.resolve(true).then(() => { + console.log('resolved'); +}) + .then(() => { + console.log('resolved'); + }); + +def = new Deferred(); + +// resolve/reject +def.resolve(10); + +def = new Deferred(); +def.reject(false); +def.promise.then(() => { + console.log('done'); +}) + .then(() => { + console.log('real done'); + }) + .catch(() => { + console.log('oops'); + }); diff --git a/types/athenajs/test/dom.ts b/types/athenajs/test/dom.ts new file mode 100644 index 0000000000..c3a0df08c0 --- /dev/null +++ b/types/athenajs/test/dom.ts @@ -0,0 +1,33 @@ +import { Dom } from 'athenajs'; + +const div = Dom('div'); +const body = Dom(document.body); +const domElt: HTMLElement = body[0]; + +// Dom.appendTo +div.appendTo(body).show().hide(); +div.appendTo(domElt); + +const str: string | null = body.css('display'); +const i: number = body.length; + +// Dom.css +body.css('display', ' block'); +body.css({ display: 'none' }); +body.css('display', 'block'); + +// Dom.find +body.find('div').appendTo('body'); + +// Dom.attr +body.attr('data-test', 'hi'); +body.attr({ 'data-test2': 'foo' }); + +// Dom.addClass/Dom.removeClass +body.addClass('foo').removeClass('foo'); + +// Dom.show/hide +body.show().hide(); + +// Dom.html +body.html('
foo
'); diff --git a/types/athenajs/test/drawable.ts b/types/athenajs/test/drawable.ts new file mode 100644 index 0000000000..7caaccaf8d --- /dev/null +++ b/types/athenajs/test/drawable.ts @@ -0,0 +1,36 @@ +import { Behavior, Drawable, Map } from 'athenajs'; + +const sprite = new Drawable('mySprite', { + objectId: 'my Rocking Sprite', + layer: 0 +}); +const sprite2 = new Drawable('mySprite2', {}); + +sprite.addChild(sprite2); +sprite.animate('Fade', { + duration: 100 +}); +sprite.moveTo(0, 0).notify('yo!'); + +sprite.onCollision(sprite2); +sprite.onEvent('boom'); +sprite.playSound('explosion', { + pan: true +}); +sprite.setBehavior('stupid'); +sprite.setBehavior(Behavior); +sprite.setScale(1.0); + +const str: string = sprite.getProperty('strProp'); +sprite.setProperty('strProp', str); + +sprite.setMask({ + x: 0, + y: 0, + width: 100, + height: 100 +}); + +sprite.setMask(null); + +sprite.stopAnimate(10); diff --git a/types/athenajs/test/game.ts b/types/athenajs/test/game.ts new file mode 100644 index 0000000000..b6505251f8 --- /dev/null +++ b/types/athenajs/test/game.ts @@ -0,0 +1,23 @@ +import { Game, Scene } from 'athenajs'; + +let myScene: Scene = new Scene(); +let sound: boolean; + +const game: Game = new Game({ + name: 'myGame', + showFps: true, + width: 320, + height: 200, + debug: true, + scene: myScene, + target: 'body', + sound: true +}); + +game.setScene(myScene); +game.toggleSound(false); +game.toggleTileInspector(true); +game.toggleFullscreen(); +game.togglePause(); +myScene = game.scene; +sound = game.sound; diff --git a/types/athenajs/test/map.ts b/types/athenajs/test/map.ts new file mode 100644 index 0000000000..a2d21ab7f0 --- /dev/null +++ b/types/athenajs/test/map.ts @@ -0,0 +1,29 @@ +import { Map, TileDesc } from 'athenajs'; + +const map = new Map({ + src: "tiles", + tileWidth: 32, + tileHeight: 32, + width: 29 * 32, + height: 8 * 32, + viewportW: 320, + viewportH: 200 +}); +const tiles: TileDesc[] = []; +const mapContent: Uint8Array = new Uint8Array(10); +const behaviors: Uint8Array = new Uint8Array(10); + +for (let row = 0; row < 8; row++) { + for (let col = 0; col < 29; col++) { + tiles.push({ + offsetX: col * 32, + offsetY: row * 32, + width: 32, + height: 32 + }); + } +} + +map.addTileSet(tiles); + +map.setData(mapContent, behaviors); diff --git a/types/athenajs/test/paint.ts b/types/athenajs/test/paint.ts new file mode 100644 index 0000000000..3b6d60b705 --- /dev/null +++ b/types/athenajs/test/paint.ts @@ -0,0 +1,12 @@ +import { Paint } from 'athenajs'; + +const paint: Paint = new Paint('brush', { + width: 300, + height: 200, + color: 'red' +}); + +paint.color = 'red'; +paint.circle(0, 0, 150); +paint.circle(0, 0, 100, 'green'); +paint.circle(0, 0, 50, 'green', 2, 'blue'); diff --git a/types/athenajs/test/scene.ts b/types/athenajs/test/scene.ts new file mode 100644 index 0000000000..c39e85f1d0 --- /dev/null +++ b/types/athenajs/test/scene.ts @@ -0,0 +1,58 @@ +import { Game, Scene, Drawable, Map } from 'athenajs'; + +let num: number; + +const hudScene = new Scene(); +const myScene: Scene = new Scene({ + name: 'myScene', + resources: [{ + id: 'myRes', + src: 'src', + type: 'image' + }], + opacity: 1, + layers: 0, + hudScene +}); + +num = myScene.getOpacity(); +myScene.setOpacity(10); + +const sprite = new Drawable('mySprite', {}); + +myScene.debug(false); +myScene.bindEvents('gameover'); +myScene.addObject(sprite); +myScene.animate('Fade', { + easing: 'linear' +}).then(() => { + console.log('effect ended'); + }); +myScene.fadeIn(1000).then(() => { + myScene.fadeOut(2000); + myScene.fadeInAndOut(2000, 1000, 1000); + console.log(myScene.getPlayTime()); +}); + +myScene.load('image', 'img/foo.png'); +myScene.load('image', 'img/background.png', 'bg'); +myScene.loadAudio('sound/yeah.mp3'); +myScene.loadImage('img/bar.gif'); +myScene.loadAudio('sound/yeah.mp3'); +myScene.loadAudio('maps/map.json'); + +myScene.setBackgroundImage(new Image()); +myScene.setLayerPriority(10, true); + +myScene.notify('ready'); + +myScene.removeObject(sprite); + +myScene.setMap({}); +myScene.setMap(new Map({ + src: 'img/tiles.png', + tileWidth: 24, + tileHeight: 32, + width: 240, + height: 320 +})); diff --git a/types/athenajs/test/simpletext.ts b/types/athenajs/test/simpletext.ts new file mode 100644 index 0000000000..2599d366c4 --- /dev/null +++ b/types/athenajs/test/simpletext.ts @@ -0,0 +1,17 @@ +import { SimpleText } from 'athenajs'; + +const myText: SimpleText = new SimpleText('MyText', { + text: 'yop', + width: 100, + height: 200, + fontFace: 'Arial', + fontSize: '20px', + fontStyle: 'italic', + fontWeight: '300', + align: 'center', + color: 'white' +}); + +myText.setSize(100, 200); +myText.setText('Hi!'); +myText.setColor('rgba(0,0,0,.4'); diff --git a/types/athenajs/test/sprite.ts b/types/athenajs/test/sprite.ts new file mode 100644 index 0000000000..bc2106c4ce --- /dev/null +++ b/types/athenajs/test/sprite.ts @@ -0,0 +1,28 @@ +import { Sprite } from 'athenajs'; + +const sprite: Sprite = new Sprite('mySprite', { + imageId: 'tiles', + animations: { + anim1: { + frameDuration: 1, + frames: [{ + offsetX: 396, + offsetY: 2, + width: 64, + height: 96, + hitBox: { + x: 1, + y: 4, + x2: 62, + y2: 95 + }, + plane: 0 + }], + loop: 0 + } + } +}); + +sprite.setAnimation('anim1', () => { + sprite.setAnimation('anim1', () => { console.log('the end!'); }, 0, true); +}); diff --git a/types/athenajs/test/tetris/flash_lines.ts b/types/athenajs/test/tetris/flash_lines.ts new file mode 100644 index 0000000000..668d2c1f33 --- /dev/null +++ b/types/athenajs/test/tetris/flash_lines.ts @@ -0,0 +1,35 @@ +import { Paint, PaintOptions } from 'athenajs'; + +interface FlashOptions extends PaintOptions { + lineHeight: number; + x: number; + y: number; + width: number; +} + +export default class FlashLines extends Paint { + lines: number[]; + lineHeight: number; + + constructor(name: string, options: FlashOptions) { + super('flashlines', options); + + this.lines = []; + this.lineHeight = options.lineHeight; + } + + flash() { + return this.animate('Fade', { + startValue: 1, + endValue: 0, + duration: 400, + loop: 2 + }); + } + + render() { + for (const line of this.lines) { + this.rect(0, line * this.lineHeight, this.width, this.lineHeight, 'white'); + } + } +} diff --git a/types/athenajs/test/tetris/grid.ts b/types/athenajs/test/tetris/grid.ts new file mode 100644 index 0000000000..ed6d96a2d0 --- /dev/null +++ b/types/athenajs/test/tetris/grid.ts @@ -0,0 +1,469 @@ +import { Scene, Map, Tile, Dom, SimpleText, AudioManager as AM, Deferred } from "athenajs"; +import Shape from "./shape"; +import FlashLines from "./flash_lines"; + +// size constants +const MAP_ROWS = 22; +const MAP_COLS = 12; +const TILE_WIDTH = 20; +const TILE_HEIGHT = 20; +// tile offsets in the spritesheet +const MAP_TILES_OFFSET_Y = 440; +const WALL_TILE_OFFSET_X = 140; +const BACK_TILE_OFFSET_X = 160; +// wall tile number +const WALL_TILE = 8; +// game width +const TOTAL_WIDTH = 800; +const TOTAL_HEIGHT = 600; +// speed (drop delay) at start +const START_TIMING = 1200; +// speed increase at each level +const LEVEL_TIMING = 55; + +class Grid extends Scene { + // game parameters + score: number; + level: number; + timing: number; + lines: number; + scoreTable: number[]; + + // game sprites + // current tetris shape + shape: Shape; + // next tetris shape + nextShape: Shape; + // next tetris string + nextString: SimpleText; + // score + scoreString: SimpleText; + // "line:" + linesString: SimpleText; + // "level:" + levelString: SimpleText; + // "pause:" + pauseString: SimpleText; + // flashing lines + flashLines: FlashLines; + // ->, <- + controls: SimpleText; + + constructor() { + super({ + resources: [ + { + id: "tiles", + type: "image", + src: "img/tetris_tiles.png" + }, + { + id: "gameover", + type: "audio", + src: "sound/gameover.mp3" + }, + { + id: "ground", + type: "audio", + src: "sound/ground.mp3" + }, + { + id: "level", + type: "audio", + src: "sound/level.mp3" + }, + { + id: "lines", + type: "audio", + src: "sound/lines.mp3" + }, + { + id: "lines_tetris", + type: "audio", + src: "sound/lines_tetris.mp3" + }, + { + id: "move", + type: "audio", + src: "sound/move.mp3" + }, + { + id: "pause", + type: "audio", + src: "sound/pause.mp3" + }, + { + id: "rotate", + type: "audio", + src: "sound/rotate.mp3" + } + ] + }); + + // here we keep game-related properties + this.score = 0; + this.level = 0; + this.lines = 0; + this.timing = START_TIMING; + this.scoreTable = [40, 100, 300, 1200]; + + // we only need to catch the 'ground' event from the 'shape' element + this.bindEvents("shape:ground"); + } + + /** + * Generate tileset for the tetris map, mostly hardcoded stuff + * + */ + generateTileSet() { + // create the list of all tiles for the map + const tiles = [ + { + offsetX: WALL_TILE_OFFSET_X, + offsetY: MAP_TILES_OFFSET_Y, + width: TILE_WIDTH, + height: TILE_HEIGHT + } + ]; + + // add a tile for each color + for (let i = 0, offset = 0; i < 7; ++i, offset += TILE_WIDTH) { + tiles.push({ + offsetX: offset, + offsetY: MAP_TILES_OFFSET_Y, + width: TILE_WIDTH, + height: TILE_HEIGHT + }); + } + + tiles.push({ + offsetX: BACK_TILE_OFFSET_X, + offsetY: MAP_TILES_OFFSET_Y, + width: TILE_WIDTH, + height: TILE_HEIGHT + }); + + return tiles; + } + + /** + * Generates the map of the game, adding walls around the playground + */ + createMap() { + // first create the map with an empty buffer + const map = new Map({ + src: "tiles", + tileWidth: TILE_WIDTH, + tileHeight: TILE_WIDTH, + width: TILE_WIDTH * MAP_COLS, + height: TILE_HEIGHT * MAP_ROWS, + buffer: new ArrayBuffer(MAP_COLS * MAP_ROWS * 2) + }); + + // finally add the tileset + map.addTileSet(this.generateTileSet()); + + return map; + } + + resetMap() { + const map = this.map; + + map.clear(0, Tile.TYPE.AIR); + + // set map tiles around the playground as wall tiles + for (let i = 0; i < map.numRows; ++i) { + map.updateTile(0, i, WALL_TILE, Tile.TYPE.WALL); + map.updateTile(map.numCols - 1, i, WALL_TILE, Tile.TYPE.WALL); + } + + for (let i = 0; i < map.numCols; ++i) { + map.updateTile(i, map.numRows - 1, WALL_TILE, Tile.TYPE.WALL); + } + } + + /** + * Generates the tile sprite that will be moved by the player + */ + createShapes() { + this.shape = new Shape("shape", { + data: { + speed: this.timing + } + }); + + this.nextShape = new Shape("nextShape", { + x: 610, + y: 110 + }); + + this.nextShape.movable = false; + this.nextString = new SimpleText("nextString", { + text: "Next", + x: 620, + y: 70 + }); + + this.scoreString = new SimpleText("scoreString", { + text: "Score: 0", + x: 50, + y: 70 + }); + + this.linesString = new SimpleText("linesString", { + text: "Lines: 0", + x: 50, + y: 120 + }); + + this.levelString = new SimpleText("levelString", { + text: "Level: 0", + x: 50, + y: 170 + }); + + this.pauseString = new SimpleText("pauseString", { + text: "Pause", + x: 380, + y: 550, + visible: false + }); + + this.controls = new SimpleText("controlsString", { + text: "Controls:\narrow keys", + x: 50, + y: 220 + }); + + this.flashLines = new FlashLines("flash", { + x: (TOTAL_WIDTH - TILE_WIDTH * MAP_COLS) / 2 + TILE_WIDTH, + y: (TOTAL_HEIGHT - TILE_HEIGHT * MAP_ROWS) / 2, + width: TILE_WIDTH * (MAP_COLS - 2), + lineHeight: TILE_HEIGHT + }); + } + + /** + * Called when the scene is ready: generates the map and adds the player's shape + * sprite onto the screen + */ + setup() { + this.createShapes(); + + this.map = this.createMap(); + } + + start() { + const map = this.map; + + this.setBackgroundImage("img/background.png"); + + // center map + this.setMap( + map, + (TOTAL_WIDTH - map.width) / 2, + (TOTAL_HEIGHT - map.height) / 2 + ); + + map.addObject(this.shape); + + this.addObject([ + this.nextShape, + this.nextString, + this.linesString, + this.scoreString, + this.levelString, + this.pauseString, + this.flashLines, + this.controls + ]); + + this.reset(); + } + + reset() { + this.score = 0; + this.level = 0; + this.lines = 0; + this.timing = START_TIMING; + this.resetMap(); + this.shape.moveToTop(); + + this.shape.setRandomShape(); + this.nextShape.setRandomShape(); + this.linesString.setText("Lines: " + this.lines); + this.scoreString.setText("Score: " + this.score); + this.levelString.setText("Level: " + this.level); + + this.shape.movable = true; + this.shape.behavior.reset(); + } + + /** + * Called on game over, simply displays the score in an alert box and restarts the game + */ + gameover() { + AM.play("gameover"); + alert("game over!" + this.score); + this.reset(); + } + + /** + * This method is called whenever an event that has been registered is received + * + */ + onEvent(event: any) { + const nextShape = this.nextShape; + const shape = this.shape; + + switch (event.type) { + case "shape:ground": + // update the map with the new shape + this.updateMap(); + // check for lines to remove + this.removeLinesFromMap( + event.data.startLine, + event.data.numRows + ).then(() => { + shape.setShape(nextShape.shapeName, nextShape.rotation); + nextShape.setRandomShape(); + + this.shape.moveToTop(); + + // we may have a game over here: if the shape collides with another one + if (!this.shape.snapTile(0, 0, false)) { + this.gameover(); + } else { + this.shape.movable = true; + } + }); + break; + } + } + + /** + * This method is called when a shape has reached the ground: in this case + * we simply update the map using the shape's matrix + */ + updateMap() { + const shape = this.shape; + const data = this.shape.shape; + const map = this.map; + const pos = map.getTileIndexFromPixel(shape.x, shape.y); + const buffer = shape.getMatrix(); + const rows = data.height / map.tileHeight; + const cols = data.width / map.tileWidth; + + for (let j = 0; j < rows; ++j) { + for (let i = 0; i < cols; ++i) { + if (buffer[j * cols + i]) { + map.updateTile(pos.x + i, pos.y + j, data.color, Tile.TYPE.WALL); + } + } + } + } + + /** + * returns the number of lines that contains no hole, starting from + * startLine up to startLine + height + * + */ + getLinesToRemove(startLine: number, height: number): number[] { + console.log("[Grid] getLinesToRemove()"); + const map = this.map; + const lines: number[] = []; + let lastLine = startLine + height - 1; + + // avoid bottom ground + if (lastLine > map.numRows - 2) lastLine = map.numRows - 2; + + for (let j = lastLine; j >= startLine; --j) { + let hole = false; + for (let i = 1; i < map.numCols - 1; ++i) { + hole = hole || map.getTileBehaviorAtIndex(i, j) !== Tile.TYPE.WALL; + } + if (!hole) { + lines.push(j); + } + } + + return lines; + } + + /** + * Updates level + level object's text + */ + updateLevel() { + const oldLevel = this.level; + this.level = Math.floor(this.lines / 10); + this.levelString.setText("Level: " + this.level); + this.timing = START_TIMING - this.level * LEVEL_TIMING; + this.shape.data['speed'] = this.timing; + oldLevel !== this.level && AM.play("level"); + } + + /** + * Updates the player's score using line number & current level + * + */ + increaseScore(lines: number) { + this.score += + this.scoreTable[lines - 1] + this.level * this.scoreTable[lines - 1]; + this.lines += lines; + this.linesString.setText("Lines: " + this.lines); + this.scoreString.setText("Score: " + this.score); + + if (lines === 4) { + AM.play("lines_tetris"); + } else { + AM.play("lines"); + } + } + + /** + * Removes lines from the map, shifting the map as needed, and adding + * empty tiles at the top + * + */ + removeLinesFromMap(startLine: number, height: number) { + const map = this.map; + const lines = this.getLinesToRemove(startLine, height); + + // no full lines detected + if (!lines.length) { + return Deferred.resolve(true); + } + + this.flashLines.lines = lines; + return this.flashLines.flash().then(() => { + // shift the map for each line to remove + for (let i = 0; i < lines.length; ++i) { + map.shift(lines[i] + i, 1); + } + + // add wall at each side of the new lines + for (let i = 0; i < height; ++i) { + for (let j = 0; j < map.numCols; ++j) { + map.updateTile(j, i, 0, Tile.TYPE.AIR); + } + map.updateTile(0, i, 8, Tile.TYPE.WALL); + map.updateTile(map.numCols - 1, i, 8, Tile.TYPE.WALL); + } + + Dom('.athena-game').addClass('shake-vertical shake-constant'); + setTimeout(() => { + Dom('.athena-game').removeClass('shake-vertical shake-constant'); + }, 300); + + this.increaseScore(lines.length); + this.updateLevel(); + }); + } + + pause(isRunning: boolean) { + this.pauseString.visible = !isRunning; + AM.play("pause"); + } +} + +export default Grid; diff --git a/types/athenajs/test/tetris/shape.ts b/types/athenajs/test/tetris/shape.ts new file mode 100644 index 0000000000..4d8ed9c64b --- /dev/null +++ b/types/athenajs/test/tetris/shape.ts @@ -0,0 +1,256 @@ +import { Sprite, Tile, AudioManager as AM, JSObject } from 'athenajs'; +import ShapeBehavior from './shape_behavior'; + +interface shapeDescription { + name: string; + width: number; + height: number; + color: number; + rotations: number[][]; +} + +class Shape extends Sprite { + shapes: shapeDescription[]; + // current shape + shape: shapeDescription; + shapeName: string; + // current shape rotation + rotation: number; + + constructor(name: string, options = {}) { + super(name, { + imageId: 'tiles', + easing: 'linear', + // behavior: ShapeBehavior, + // ...options + }); + + /** + * Hardcoded tetris shapes. In addition to its width/height, color and + * name, each shape contains a rotation a matrix for each rotation + * that looks like: + * + * --- + * |J + * |JJJ + * | + * --- + * + * Matrix: [1, 0, 0, + * 1, 1, 1 + * 0, 0, 0] + * + * Each shape contains four different rotations + */ + this.shapes = [ + { + name: 'I', width: 80, height: 80, color: 7, rotations: [ + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0] + ] + }, + { + name: 'J', width: 60, height: 60, color: 6, rotations: [ + [1, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 0, 1, 0, 0, 1, 0], + [0, 0, 0, 1, 1, 1, 0, 0, 1], + [0, 1, 0, 0, 1, 0, 1, 1, 0] + ] + }, + { + name: 'L', width: 60, height: 60, color: 5, rotations: [ + [0, 0, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 0, 0, 1, 0, 0, 1, 1], + [0, 0, 0, 1, 1, 1, 1, 0, 0], + [1, 1, 0, 0, 1, 0, 0, 1, 0] + ] + }, + { + name: 'O', width: 80, height: 60, color: 4, rotations: [ + [0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0] + ] + }, + { + name: 'S', width: 60, height: 60, color: 3, rotations: [ + [0, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 1, 1, 0, 0, 1], + [0, 0, 0, 0, 1, 1, 1, 1, 0], + [1, 0, 0, 1, 1, 0, 0, 1, 0] + ] + }, + { + name: 'Z', width: 60, height: 60, color: 2, rotations: [ + [1, 1, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 1, 1, 0, 0, 1, 1], + [0, 1, 0, 1, 1, 0, 1, 0, 0] + ] + }, + { + name: 'T', width: 60, height: 60, color: 1, rotations: [ + [0, 1, 0, 1, 1, 1, 0, 0, 0], + [0, 1, 0, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 1, 1, 1, 0, 1, 0], + [0, 1, 0, 1, 1, 0, 0, 1, 0] + ] + } + ]; + + this.addAnimations(); + this.setShape('S', 0); + } + + /** + * Moves the shape at the top center of the map + */ + moveToTop(): void { + if (this.shape) { + const map = this.currentMap; + const col = Math.floor(((map.width - this.shape.width) / 2) / map.tileWidth); + + this.moveTo(col * map.tileWidth, 0); + } + } + + /** + * Changes the sprite's shape and rotation + * + */ + setShape(name: string, rotation: number): void { + this.shapeName = name; + this.rotation = rotation; + this.shape = this.shapes.find((shape) => shape.name === this.shapeName) || this.shapes[0]; + this.setAnimation(`${name}${rotation}`); + } + + /** + * Pick a new random shape + */ + setRandomShape(): void { + const shapeName = this.shapes[Math.random() * 7 | 0].name; + const rotation = Math.random() * 4 | 0; + + console.log(`[Shape] setRandomShape() - ${shapeName}`); + + if (!this.movable) { + this.animate('Fade', { + duration: 200, + startValue: 1, + endValue: 0 + }).then(() => { + this.setShape(shapeName, rotation); + this.animate('Fade', { + duration: 200, + startValue: 0, + endValue: 1 + }); + }); + } else { + this.setShape(shapeName, rotation); + } + } + + /** + * Returns current matrix for the shape + * + */ + getMatrix(rotation = -1): number[] { + return this.shape.rotations[rotation === -1 ? this.rotation : rotation]; + } + + /** + * Move the shape on the map by a certain number of tiles, optionnaly sending an event + * of a collision is detected + * + */ + snapTile(horizontal = 0, vertical = 0, notify = true, noSound = false): boolean { + const map = this.currentMap; + const buffer = this.getMatrix(); + const tilePos = map.getTileIndexFromPixel(this.x, this.y); + const newX = tilePos.x + horizontal; + const newY = tilePos.y + vertical; + + // first check there is no collision with walls + if (!map.checkMatrixForCollision(buffer, this.shape.width, newX, newY, Tile.TYPE.WALL)) { + this.x += horizontal * map.tileWidth; + this.y += vertical * map.tileHeight; + + return true; + } else { + // if a collision was detected and vertical == 1 it means the shape reached + // the ground: in this case we send a notification for the grid + // and make the shape stop responding to user input or timer + if (vertical === 1) { + this.movable = false; + if (notify) { + AM.play('ground'); + this.notify('ground', { + startLine: tilePos.y, + numRows: this.shape.height / map.tileHeight + }); + } + } + return false; + } + } + + /** + * Switches to the next shape's rotation, if no collision found onto the map + */ + nextRotation(): void { + let matrix: number[]; + let newRotation = this.rotation + 1; + + const map = this.currentMap; + const tilePos = map.getTileIndexFromPixel(this.x, this.y); + + // cycles if last position reached + if (newRotation > 3) { + newRotation = 0; + } + + // get current shape + position matrix + matrix = this.getMatrix(newRotation); + + if (!map.checkMatrixForCollision(matrix, this.shape.width, tilePos.x, tilePos.y, Tile.TYPE.WALL)) { + // change shape rotation if no collision detected + this.setShape(this.shapeName, newRotation); + AM.play('rotate'); + } else { + console.log('rotation not possible'); + } + } + + /** + * We add a new Sprite animation for each combination of rotation + shapeType: + * { + * 'J0', // first rotation of the J Shape + * .... + * 'J3', // last rotation of the J Shape + * 'L0', // first rotation of the L shape + * ... + * } + */ + addAnimations(): void { + // shape sprite images start at the top of the image file + let offsetY = 0; + + this.shapes.forEach((shape) => { + let offsetX = 0; + for (let i = 0; i < 4; ++i) { + this.addAnimation(`${shape.name}${i}`, 'tiles', { + offsetY, offsetX, frameWidth: shape.width, frameHeight: shape.height, frameDuration: 1, numFrames: 1 + }); + offsetX += shape.width; + } + offsetY += shape.height; + }); + } +} + +export default Shape; diff --git a/types/athenajs/test/tetris/shape_behavior.ts b/types/athenajs/test/tetris/shape_behavior.ts new file mode 100644 index 0000000000..7804696454 --- /dev/null +++ b/types/athenajs/test/tetris/shape_behavior.ts @@ -0,0 +1,140 @@ +import { Behavior, InputManager as IM, AudioManager as AM, Sprite, Drawable } from 'athenajs'; +import Shape from './shape'; + +/** + * Simple Behavior for the tetris shape that moves the shape on cursor key press + * and when timer is reached + * + * + * @see {Behavior} + */ +class ShapeBehavior extends Behavior { + state: number; + lastRotation: number; + ts: number; + LONG_DELAY: number; + SMALL_DELAY: number; + delay: number; + key: number; + timerEnabled: boolean; + startTime: number; + + constructor(sprite: Shape, options?: any) { + super(sprite as Drawable, options); + + // current behavior state: moving right, left, top, bottom + this.state = 0; + // when lastRotation happened + this.lastRotation = 0; + this.ts = 0; + // long delay before starting to move quickly + this.LONG_DELAY = 250; + // repeat-delay once the long delay has been reached + this.SMALL_DELAY = 70; + + this.reset(); + } + + reset() { + IM.clearEvents(); + + // current delay before repeating a key + this.delay = this.LONG_DELAY; + this.key = 0; + this.timerEnabled = true; + } + + /** + * When the player keeps a key down, we wait for a long delay before + * quickly moving the picece: we don't want to miss interpret his move. + * + * If he quickly releases the key and quickly presses it, we have to + * react though + * + */ + ready(state: number, timestamp: number): boolean { + // if the player pressed a different key + // we react immediately but have to wait a long_delay + // before repeating the key if he keeps pressing it + if (this.state !== state) { + this.ts = timestamp; + this.state = state; + this.delay = this.LONG_DELAY; + return true; + } else if (timestamp - this.ts > this.delay) { + // player keeps pressing the key for a long delay + // we react and set delay to a smaller one to quickly + // repeat the action + this.ts = timestamp; + this.delay = this.SMALL_DELAY; + return true; + } else { + // repeat delay not reached + return false; + } + } + + /** + * Checks tetris timer + * + */ + timer(timestamp: number): boolean { + const sprite = this.sprite; + if (!this.startTime) { + this.startTime = timestamp; + } else { + if (timestamp - this.startTime > sprite.data['speed']) { + // timer reached + this.startTime = timestamp; + return true; + } + } + return false; + } + + checkKeyDelay(key: number, timestamp: number, x: number, y: number): void { + const sprite = this.sprite as Shape; + if (this.ready(key, timestamp)) { + sprite.snapTile(x, y) && AM.play('move'); + } + } + + /** + * This method is called when updating the shape's position + * and updates its position when cursor keys are pressed or + * the timer happened + */ + onUpdate(timestamp: number) { + const sprite = this.sprite as Shape; + + // debug: stop the timer when t key is pressed + if (IM.isKeyDown(84)) { + this.timerEnabled = !this.timerEnabled; + return; + } + + // first check timer + if (this.timerEnabled && this.timer(timestamp)) { + // timer reached: move the sprite down + sprite.snapTile(0, 1); + return; + } + + // Then checks cursor keys + if (IM.isKeyDown('DOWN')) { + this.checkKeyDelay(1, timestamp, 0, 1); + } else if (IM.isKeyDown('LEFT')) { + this.checkKeyDelay(2, timestamp, -1, 0); + } else if (IM.isKeyDown('RIGHT')) { + this.checkKeyDelay(3, timestamp, 1, 0); + } else if ((IM.isKeyDown('UP') || IM.isKeyDown('SPACE')) && (timestamp - this.lastRotation > 150)) { + this.lastRotation = timestamp; + sprite.nextRotation(); + } else if (this.state) { + // key released + this.ready(0, timestamp); + } + } +} + +export default ShapeBehavior; diff --git a/types/athenajs/test/tetris/tetris.ts b/types/athenajs/test/tetris/tetris.ts new file mode 100644 index 0000000000..05434a2249 --- /dev/null +++ b/types/athenajs/test/tetris/tetris.ts @@ -0,0 +1,11 @@ +import { Game } from 'athenajs'; +import Grid from './grid'; + +const tetris = new Game({ + name: 'athena-tetris', + showFps: true, + debug: true, + width: 800, + height: 600, + scene: new Grid() +}); diff --git a/types/athenajs/tsconfig.json b/types/athenajs/tsconfig.json new file mode 100644 index 0000000000..54a31f17a7 --- /dev/null +++ b/types/athenajs/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "noEmit": true, + "types": [], + "lib": [ + "es5", + "es6", + "es2015.iterable", + "dom" + ], + "module": "commonjs", + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "test/dom.ts", + "test/deferred.ts", + "test/game.ts", + "test/scene.ts", + "test/drawable.ts", + "test/simpletext.ts", + "test/paint.ts", + "test/bitmaptext.ts", + "test/sprite.ts", + "test/map.ts", + "test/tetris/tetris.ts", + "test/tetris/shape.ts", + "test/tetris/grid.ts", + "test/tetris/shape_behavior.ts", + "test/tetris/flash_lines.ts" + ] +} \ No newline at end of file diff --git a/types/athenajs/tslint.json b/types/athenajs/tslint.json new file mode 100644 index 0000000000..e60c15844f --- /dev/null +++ b/types/athenajs/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +} \ No newline at end of file