Replace all CoffeeScript code by TypeScript
This commit is contained in:
parent
4fa246628b
commit
c64ead08a3
24 changed files with 315 additions and 228 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/build
|
||||
/node_modules
|
||||
/typings
|
||||
|
|
10
package.json
10
package.json
|
@ -4,16 +4,14 @@
|
|||
"start": "webpack-dev-server -d --content-base static",
|
||||
"build": "webpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coffee-loader": "^0.7.2",
|
||||
"coffee-script": "^1.10.0",
|
||||
"dependencies": {
|
||||
"css-loader": "^0.23.1",
|
||||
"html-webpack-plugin": "^1.7.0",
|
||||
"lodash": "^3.10.1",
|
||||
"style-loader": "^0.13.0",
|
||||
"ts-loader": "^0.7.2",
|
||||
"typescript": "^1.7.5",
|
||||
"webpack": "^1.12.9",
|
||||
"webpack-dev-server": "^1.14.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^3.10.1"
|
||||
}
|
||||
}
|
||||
|
|
5
require.d.ts
vendored
Normal file
5
require.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare var require: {
|
||||
<T>(path: string): T;
|
||||
(paths: string[], callback: (...modules: any[]) => void): void;
|
||||
ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
require './style.css'
|
||||
|
||||
window._ = require 'lodash'
|
||||
|
||||
MapData = require './model/MapData'
|
||||
MapContext = require './control/MapContext'
|
||||
|
||||
|
||||
mapContext = null
|
||||
|
||||
|
||||
window.onload = ->
|
||||
xhr = new XMLHttpRequest()
|
||||
xhr.onload = ->
|
||||
mapDef = new MapData(JSON.parse this.responseText)
|
||||
mapContext = new MapContext mapDef
|
||||
|
||||
xhr.open 'GET', 'resources/map/test.json', true
|
||||
xhr.send()
|
26
src/app.ts
Normal file
26
src/app.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
require('./style.css');
|
||||
|
||||
|
||||
import * as lodash from 'lodash';
|
||||
_ = lodash;
|
||||
|
||||
import MapContext from './control/MapContext';
|
||||
import MapData from './model/MapData';
|
||||
|
||||
|
||||
var mapContext: MapContext;
|
||||
|
||||
window.onload = () => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onload = function() {
|
||||
let mapDef = new MapData(JSON.parse(this.responseText));
|
||||
mapContext = new MapContext(mapDef);
|
||||
}
|
||||
|
||||
xhr.open('GET', 'resources/map/test.json', true);
|
||||
xhr.send();
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
Direction = require '../model/Direction'
|
||||
Entity = require '../model/Entity'
|
||||
EntityPosition = require '../model/EntityPosition'
|
||||
Position = require '../model/Position'
|
||||
|
||||
MapView = require '../view/MapView'
|
||||
|
||||
|
||||
class MapContext
|
||||
constructor: (@map) ->
|
||||
@entities = {}
|
||||
|
||||
@playerEntity = new EntityPosition(
|
||||
new Entity('square'),
|
||||
new Position(8, 8),
|
||||
Direction.EAST)
|
||||
|
||||
@addEntity(@playerEntity)
|
||||
|
||||
@mavView = new MapView @map, @entities
|
||||
|
||||
addEntity: (entity) =>
|
||||
@entities[entity.position.asString()] = entity
|
||||
|
||||
|
||||
module.exports = MapContext
|
34
src/control/MapContext.ts
Normal file
34
src/control/MapContext.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
import Direction from '../model/Direction';
|
||||
import Entity from '../model/Entity';
|
||||
import EntityPosition from '../model/EntityPosition';
|
||||
import MapData from '../model/MapData';
|
||||
import Position from '../model/Position';
|
||||
|
||||
import MapView from '../view/MapView';
|
||||
|
||||
|
||||
export default class MapContext {
|
||||
view: MapView;
|
||||
|
||||
entities: {[key: string]: EntityPosition} = {};
|
||||
playerEntity: EntityPosition;
|
||||
|
||||
constructor(public map: MapData) {
|
||||
this.playerEntity = new EntityPosition(
|
||||
new Entity('square'),
|
||||
new Position(8, 8),
|
||||
Direction.East
|
||||
);
|
||||
|
||||
this.addEntity(this.playerEntity);
|
||||
|
||||
this.view = new MapView(map, this.entities);
|
||||
}
|
||||
|
||||
addEntity(entity: EntityPosition) {
|
||||
this.entities[entity.position.asString()] = entity;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
Direction =
|
||||
NORTH: 0
|
||||
EAST: 1
|
||||
SOUTH: 2
|
||||
WEST: 3
|
||||
|
||||
reverse: (d) -> (d+2)%4
|
||||
|
||||
|
||||
module.exports = Direction
|
13
src/model/Direction.ts
Normal file
13
src/model/Direction.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
export enum Direction {
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West
|
||||
};
|
||||
|
||||
export function reverse(r: Direction): Direction { return (r+2) % 4; }
|
||||
|
||||
export default Direction;
|
|
@ -1,8 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
class Entity
|
||||
constructor: (@name) ->
|
||||
|
||||
|
||||
module.exports = Entity
|
6
src/model/Entity.ts
Normal file
6
src/model/Entity.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
export default class Entity {
|
||||
constructor(public name: string) {}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
class EntityPosition
|
||||
constructor: (@entity, @position, @direction) ->
|
||||
|
||||
|
||||
module.exports = EntityPosition
|
11
src/model/EntityPosition.ts
Normal file
11
src/model/EntityPosition.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
import Direction from '../model/Direction';
|
||||
import Entity from './Entity';
|
||||
import Position from './Position';
|
||||
|
||||
|
||||
export default class EntityPosition {
|
||||
constructor(public entity: Entity, public position: Position, public direction: Direction) {}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
class MapData
|
||||
constructor: (data) ->
|
||||
{@tiles, @collition, @layers} = data
|
||||
|
||||
|
||||
module.exports = MapData
|
20
src/model/MapData.ts
Normal file
20
src/model/MapData.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
interface Input {
|
||||
tiles: {[key: string]: {file: string}};
|
||||
collision: string[];
|
||||
layers: string[][][];
|
||||
}
|
||||
|
||||
export default class MapData {
|
||||
tiles: {[key: string]: {file: string}};
|
||||
public collision: string[];
|
||||
public layers: string[][][];
|
||||
|
||||
constructor(data: Input) {
|
||||
this.tiles = data.tiles;
|
||||
this.collision = data.collision;
|
||||
this.layers = data.layers;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
class Position
|
||||
constructor: (@x, @y) ->
|
||||
|
||||
asString: => "#{@x},#{@y}"
|
||||
|
||||
|
||||
module.exports = Position
|
10
src/model/Position.ts
Normal file
10
src/model/Position.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
export default class Position {
|
||||
constructor(public x: number, public y: number) {}
|
||||
|
||||
asString(): string {
|
||||
return `${this.x},${this.y}`;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
module.exports =
|
||||
mapPromises: (promises) ->
|
||||
p = []
|
||||
ret = {}
|
||||
|
||||
for own k, v of promises
|
||||
do (k, v) ->
|
||||
p.push(v.then (r) -> ret[k] = r)
|
||||
|
||||
Promise.all(p).then -> ret
|
13
src/util.ts
Normal file
13
src/util.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
export function mapPromises<T>(promises: {[key: string]: Promise<T>}): Promise<{[key: string]: T}> {
|
||||
var p: Promise<void>[] = []
|
||||
var ret: {[key: string]: T} = {}
|
||||
|
||||
_.forOwn(promises, (v, k) => {
|
||||
p.push(v.then(r => {ret[k] = r;}));
|
||||
});
|
||||
|
||||
return Promise.all(p).then(() => ret);
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
util = require '../util'
|
||||
|
||||
|
||||
tileSize = 32
|
||||
|
||||
body = document.getElementsByTagName('body')[0]
|
||||
|
||||
|
||||
loadImage = (url) ->
|
||||
new Promise (resolve, reject) ->
|
||||
img = new Image()
|
||||
img.addEventListener 'load', -> resolve img
|
||||
img.addEventListener 'error', -> reject Error('Failed to load ' + url)
|
||||
img.src = url
|
||||
|
||||
loadImages = (imgs) ->
|
||||
util.mapPromises(_.mapValues imgs, loadImage)
|
||||
|
||||
loadTiles = (tiles) ->
|
||||
loadImages(_.mapValues tiles, (t) -> "resources/sprite/tile/#{t.file}.png")
|
||||
|
||||
loadEntities = (entities) ->
|
||||
p = {}
|
||||
for e in entities
|
||||
do (e) ->
|
||||
p[e.entity.name] = loadImage "resources/sprite/entity/#{e.entity.name}.png"
|
||||
|
||||
util.mapPromises p
|
||||
|
||||
|
||||
class MapView
|
||||
constructor: (@map, @entities) ->
|
||||
@redrawPending = false
|
||||
|
||||
@canvas = document.createElement 'canvas'
|
||||
@canvas.style.position = 'absolute'
|
||||
body.appendChild @canvas
|
||||
|
||||
@ctx = @canvas.getContext '2d'
|
||||
|
||||
window.addEventListener 'resize', @setSize
|
||||
@setSize()
|
||||
|
||||
tilesReady = loadTiles(@map.tiles).then (tiles) =>
|
||||
@tiles = tiles
|
||||
|
||||
entitiesReady = loadEntities(_.values @entities).then (entities) =>
|
||||
@entitySprites = entities
|
||||
|
||||
tilesReady.then(entitiesReady).then =>
|
||||
@redraw()
|
||||
return
|
||||
|
||||
drawTile: (x, y, tile) =>
|
||||
return unless tile
|
||||
|
||||
@ctx.drawImage tile, x, y
|
||||
|
||||
drawEntity: (e) =>
|
||||
sprite = @entitySprites[e.entity.name]
|
||||
return unless sprite
|
||||
|
||||
@ctx.drawImage(
|
||||
sprite,
|
||||
e.direction*tileSize, 0,
|
||||
tileSize, tileSize,
|
||||
e.position.x*tileSize, e.position.y*tileSize,
|
||||
tileSize, tileSize)
|
||||
|
||||
draw: =>
|
||||
@redrawPending = false
|
||||
|
||||
@ctx.clearRect 0, 0, @canvas.width, @canvas.height
|
||||
|
||||
return unless @tiles and @entitySprites
|
||||
|
||||
for layer in @map.layers
|
||||
y = 0
|
||||
|
||||
for row in layer
|
||||
x = 0
|
||||
|
||||
for tile in row
|
||||
@drawTile x, y, @tiles[tile]
|
||||
x += tileSize
|
||||
|
||||
y += tileSize
|
||||
|
||||
for e in _.values @entities
|
||||
@drawEntity e
|
||||
|
||||
redraw: =>
|
||||
unless @redrawPending
|
||||
@redrawPending = true
|
||||
window.requestAnimationFrame @draw
|
||||
|
||||
setSize: =>
|
||||
e = document.documentElement
|
||||
@canvas.width = window.innerWidth || e.clientWidth || body.clientWidth
|
||||
@canvas.height = window.innerHeight || e.clientHeight || body.clientHeight
|
||||
|
||||
@redraw()
|
||||
|
||||
|
||||
|
||||
module.exports = MapView
|
142
src/view/MapView.ts
Normal file
142
src/view/MapView.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
import * as util from '../util';
|
||||
import EntityPosition from '../model/EntityPosition';
|
||||
import MapData from '../model/MapData';
|
||||
|
||||
|
||||
const tileSize = 32;
|
||||
|
||||
const body = document.getElementsByTagName('body')[0];
|
||||
|
||||
|
||||
function loadImage(url: string): Promise<HTMLImageElement> {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var img = new Image();
|
||||
img.addEventListener('load', () => { resolve(img); });
|
||||
img.addEventListener('error', () => { reject(Error('Failed to load ' + url)); });
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function loadImages(imgs: {[key: string]: string}): Promise<{[key: string]: HTMLImageElement}> {
|
||||
return util.mapPromises(_.mapValues(imgs, loadImage));
|
||||
}
|
||||
|
||||
function loadTiles(tiles: {[key: string]: {file: string}}): Promise<{[key: string]: HTMLImageElement}> {
|
||||
return loadImages(_.mapValues(tiles, (t) => `resources/sprite/tile/${t.file}.png`));
|
||||
}
|
||||
|
||||
function loadEntities(entities: EntityPosition[]): Promise<{[key: string]: HTMLImageElement}> {
|
||||
var p: {[key: string]: Promise<HTMLImageElement>} = {};
|
||||
|
||||
entities.forEach(e => {
|
||||
p[e.entity.name] = loadImage(`resources/sprite/entity/${e.entity.name}.png`);
|
||||
});
|
||||
|
||||
return util.mapPromises(p);
|
||||
}
|
||||
|
||||
|
||||
export default class MapView {
|
||||
redrawPending: boolean = false;
|
||||
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
||||
tiles: {[key: string]: HTMLImageElement};
|
||||
entitySprites: {[key: string]: HTMLImageElement};
|
||||
|
||||
constructor(private map: MapData, private entities: {[key: string]: EntityPosition}) {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.canvas.style.position = 'absolute';
|
||||
body.appendChild(this.canvas);
|
||||
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
|
||||
window.addEventListener('resize', () => this.setSize());
|
||||
this.setSize();
|
||||
|
||||
var tilesReady = loadTiles(map.tiles).then((tiles) => {
|
||||
this.tiles = tiles;
|
||||
});
|
||||
|
||||
var entitiesReady = loadEntities(this.getEntities()).then((entities) => {
|
||||
this.entitySprites = entities;
|
||||
});
|
||||
|
||||
Promise.all([tilesReady, entitiesReady]).then(() => {
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
getEntities(): EntityPosition[] {
|
||||
return _.valuesIn<EntityPosition>(this.entities);
|
||||
}
|
||||
|
||||
setSize() {
|
||||
var e = document.documentElement;
|
||||
this.canvas.width = window.innerWidth || e.clientWidth || body.clientWidth;
|
||||
this.canvas.height = window.innerHeight || e.clientHeight || body.clientHeight;
|
||||
|
||||
this.redraw()
|
||||
}
|
||||
|
||||
drawTile(x: number, y: number, tile: HTMLImageElement) {
|
||||
if (!tile)
|
||||
return;
|
||||
|
||||
this.ctx.drawImage(tile, x, y);
|
||||
}
|
||||
|
||||
drawEntity(e: EntityPosition) {
|
||||
var sprite = this.entitySprites[e.entity.name];
|
||||
if (!sprite)
|
||||
return;
|
||||
|
||||
this.ctx.drawImage(
|
||||
sprite,
|
||||
e.direction*tileSize, 0,
|
||||
tileSize, tileSize,
|
||||
e.position.x*tileSize, e.position.y*tileSize,
|
||||
tileSize, tileSize
|
||||
);
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.redrawPending = false;
|
||||
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
if (!this.tiles || !this.entitySprites)
|
||||
return;
|
||||
|
||||
this.map.layers.forEach((layer) => {
|
||||
let y = 0;
|
||||
|
||||
layer.forEach((row) => {
|
||||
let x = 0;
|
||||
|
||||
for (let tile in row) {
|
||||
this.drawTile(x, y, this.tiles[row[tile]]);
|
||||
x += tileSize;
|
||||
}
|
||||
|
||||
y += tileSize;
|
||||
});
|
||||
});
|
||||
|
||||
this.getEntities().forEach(e => {
|
||||
this.drawEntity(e);
|
||||
});
|
||||
}
|
||||
|
||||
redraw() {
|
||||
if (this.redrawPending)
|
||||
return;
|
||||
|
||||
this.redrawPending = true;
|
||||
window.requestAnimationFrame(() => this.draw());
|
||||
}
|
||||
}
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"require.d.ts",
|
||||
"typings/tsd.d.ts"
|
||||
]
|
||||
}
|
15
tsd.json
Normal file
15
tsd.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version": "v4",
|
||||
"repo": "borisyankov/DefinitelyTyped",
|
||||
"ref": "master",
|
||||
"path": "typings",
|
||||
"bundle": "typings/tsd.d.ts",
|
||||
"installed": {
|
||||
"lodash/lodash.d.ts": {
|
||||
"commit": "f11e49cfb77f99678657ddfe5d43017849675c64"
|
||||
},
|
||||
"es6-promise/es6-promise.d.ts": {
|
||||
"commit": "f11e49cfb77f99678657ddfe5d43017849675c64"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/app.coffee',
|
||||
entry: './src/app.ts',
|
||||
output: {
|
||||
path: './build',
|
||||
filename: 'bundle.js'
|
||||
|
@ -14,10 +14,10 @@ module.exports = {
|
|||
module: {
|
||||
loaders: [
|
||||
{ test: /\.css$/, loader: 'style-loader!css-loader' },
|
||||
{ test: /\.coffee$/, loader: 'coffee-loader' }
|
||||
{ test: /\.ts$/, loader: 'ts-loader' }
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.json', '.coffee']
|
||||
extensions: ['', '.ts']
|
||||
}
|
||||
};
|
||||
|
|
Reference in a new issue