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
This commit is contained in:
Nicolas Ramz 2018-10-02 06:24:20 +02:00 committed by Wesley Wigham
parent 52fe47713e
commit e48af07ef9
18 changed files with 2020 additions and 0 deletions

784
types/athenajs/index.d.ts vendored Normal file
View File

@ -0,0 +1,784 @@
// Type definitions for athenajs 0.1
// Project: https://github.com/AthenaJS/athenajs
// Definitions by: Nicolas Ramz <https://github.com/warpdesign>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export as namespace AthenaJS;
export function Dom(sel?: string | HTMLElement): _Dom<HTMLElement>;
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<TElement> extends Iterable<TElement> {
[key: number]: TElement;
length: number;
css(prop: string, val: string): _Dom<TElement>;
css(prop: JSObject): _Dom<TElement>;
css(prop: string): string|null;
find(selector: string): _Dom<TElement>;
appendTo(selector: string | _Dom<TElement> | HTMLElement): _Dom<TElement>;
attr(att: string, val: string): _Dom<TElement>;
attr(att: JSObject): _Dom<TElement>;
addClass(classes: string): _Dom<TElement>;
removeClass(classes: string): _Dom<TElement>;
html(str: string): _Dom<TElement>;
show(): _Dom<TElement>;
hide(): _Dom<TElement>;
}
/* 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;
}

View File

@ -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();

View File

@ -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');
});

View File

@ -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('<div>foo</div>');

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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');

View File

@ -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
}));

View File

@ -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');

View File

@ -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);
});

View File

@ -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');
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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()
});

View File

@ -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"
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "dtslint/dt.json"
}