4 | JavaScript for game development
5 |
6 |
7 |
8 |
9 |
10 |
JavaScript for game development
11 |
12 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var jasmine = require('gulp-jasmine');
3 | var eslint = require('gulp-eslint');
4 |
5 | gulp.task('watch', ['test'], function () {
6 | gulp.watch('./src/**/*', ['test']);
7 | gulp.watch('./spec/**/*', ['test']);
8 | });
9 |
10 | gulp.task('lint', function () {
11 | gulp.src('./src/**/*')
12 | .pipe(eslint())
13 | .pipe(eslint.format());
14 | });
15 |
16 | gulp.task('test', ['lint'], function () {
17 | gulp.src('./spec/**/*')
18 | .pipe(jasmine({ includeStackTrace: true }));
19 | });
20 |
21 |
22 | gulp.task('default', ['test']);
23 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/Options.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var EventEmitter = require('events').EventEmitter;
4 |
5 | function Options(group) {
6 | EventEmitter.call(this);
7 | this._group = typeof group === 'object' ? group : {};
8 | }
9 | Options.prototype = Object.create(EventEmitter.prototype);
10 | Options.prototype.constructor = Options;
11 |
12 | Options.prototype.list = function () {
13 | return Object.keys(this._group);
14 | };
15 |
16 | Options.prototype.get = function (id) {
17 | return this._group[id];
18 | };
19 |
20 | Options.prototype.select = function (id) {
21 | // Haz que se emita un evento cuando seleccionamos una opción.
22 | };
23 |
24 | module.exports = Options;
25 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pvli2017-rpg-battle",
3 | "version": "1.0.0",
4 | "description": "Skeleton for practise 0 of PVLI course 2017",
5 | "main": "index.js",
6 | "scripts": {
7 | "bundle": "browserify export.js --standalone RPG > rpg.js",
8 | "test": "node ./node_modules/gulp/bin/gulp.js test",
9 | "watch": "node ./node_modules/gulp/bin/gulp.js watch"
10 | },
11 | "author": "Salvador de la Puente González ",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "browserify": "^13.1.0",
15 | "gulp": "^3.9.1",
16 | "gulp-eslint": "^3.0.1",
17 | "gulp-jasmine": "^2.4.2",
18 | "mockery": "^2.0.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/TurnList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function TurnList() {}
4 |
5 | TurnList.prototype.reset = function (charactersById) {
6 | this._charactersById = charactersById;
7 |
8 | this._turnIndex = -1;
9 | this.turnNumber = 0;
10 | this.activeCharacterId = null;
11 | this.list = this._sortByInitiative();
12 | };
13 |
14 | TurnList.prototype.next = function () {
15 | // Haz que calcule el siguiente turno y devuelva el resultado
16 | // según la especificación. Recuerda que debe saltar los personajes
17 | // muertos.
18 | };
19 |
20 | TurnList.prototype._sortByInitiative = function () {
21 | // Utiliza la función Array.sort(). ¡No te implementes tu propia
22 | // función de ordenación!
23 | };
24 |
25 | module.exports = TurnList;
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript for game development
2 |
3 | A compilation of materials to learn JavaScript and make HTML5 games.
4 | You can also [find it online](https://mozdevs.github.io/js-for-gamedev/es/).
5 |
6 | ## Build the ebook
7 |
8 | This project uses **Node** and **npm scripts** to build and perform some tasks. You will need to have both Node and npm installed in your system.
9 |
10 | 1. Install third-party dependencies
11 |
12 | ```sh
13 | npm install
14 | ```
15 | 2. Build the HTML ebooks in all available languages with:
16 |
17 | ```sh
18 | npm run build
19 | ```
20 |
21 | ## Other tasks
22 |
23 | You can clean up the files generated by the build process with:
24 |
25 | ```sh
26 | npm run clean
27 | ```
28 |
29 | You can publish the HTML ebooks via Github Pages with:
30 |
31 | ```sh
32 | npm run deploy
33 | ```
34 |
--------------------------------------------------------------------------------
/scripts/generate-zips.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var archiver = require('archiver');
4 | var path = require('path');
5 | var fs = require('fs');
6 |
7 | // add here all the dirs that will be zipped
8 | const dirs = [
9 | 'es/02-javascript/02-practica/start-here',
10 | 'es/03-javascript-en-el-navegador/03-practica/start-here'
11 | ];
12 |
13 | dirs.forEach(function (src) {
14 | let zipFile = archiver('zip');
15 | let filename = path.join(path.dirname(src), `${path.basename(src)}.zip`);
16 |
17 | let output = fs.createWriteStream(filename);
18 | output.on('close', function () {
19 | console.log(`${filename} - ${zipFile.pointer()} total bytes`);
20 | });
21 |
22 | zipFile.pipe(output);
23 | zipFile.glob('**/*', {
24 | expand: true,
25 | cwd: src,
26 | dot: true
27 | });
28 | zipFile.finalize();
29 | });
30 |
--------------------------------------------------------------------------------
/es/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Índice
2 |
3 |
4 | - [El entorno de trabajo](01-intro/index.md)
5 |
6 | - JavaScript básico
7 | - [Programación orientada a objetos](02-javascript/0201-poo/index.md)
8 | - [El modelo de datos de JavaScript](02-javascript/0202-modelo-de-datos/index.md)
9 | - [El modelo de ejecución de JavaScript](02-javascript/0203-modelo-de-ejecucion/index.md)
10 | - [Ejercicios guiados](02-javascript/02-ejercicios/index.md)
11 | - [Práctica](02-javascript/02-practica/index.md)
12 | - [Guía](02-javascript/02-practica/GUIDE.md)
13 | - [TDD](02-javascript/02-practica/TDD.md)
14 |
15 | - JavaScript en el navegador
16 |
17 | - [Navegador y DOM](03-javascript-en-el-navegador/0301-dom/index.md)
18 | - [Canvas](03-javascript-en-el-navegador/0302-canvas/index.md)
19 | - [Ejercicios guiados](03-javascript-en-el-navegador/03-ejercicios/index.md)
20 | - [Práctica](03-javascript-en-el-navegador/03-practica/index.md)
21 | - [Guía](03-javascript-en-el-navegador/03-practica/guia.md)
22 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/OptionsStack.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Options = require('./Options');
3 |
4 | function OptionsStack() {
5 | this._stack = [];
6 | Object.defineProperty(this, 'current', {
7 | get: function () {
8 | return this._stack[this._stack.length - 1];
9 | },
10 | set: function (v) {
11 | if (!(v instanceof Options)) {
12 | v = new Options(v);
13 | }
14 | return this._stack.push(v);
15 | }
16 | });
17 | }
18 |
19 | OptionsStack.prototype.select = function (id) {
20 | // Redirige el comando al último de la pila.
21 | };
22 |
23 | OptionsStack.prototype.list = function () {
24 | // Redirige el comando al último de la pila.
25 | };
26 |
27 | OptionsStack.prototype.get = function (id) {
28 | return this.current.get(id);
29 | };
30 |
31 | OptionsStack.prototype.cancel = function () {
32 | this._stack.pop();
33 | };
34 |
35 | OptionsStack.prototype.clear = function () {
36 | this._stack = [];
37 | };
38 |
39 | module.exports = OptionsStack;
40 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Utils module', function () {
4 | var utils = require('../src/utils');
5 |
6 | it('has a listToMap() to convert from a list to an object choosing the key. ',
7 | function () {
8 | var list = [
9 | { name: 'a', hp: 1 },
10 | { name: 'b', hp: 2 },
11 | { name: 'c', hp: 3 }
12 | ];
13 | expect(utils.listToMap(list, useName)).toEqual({
14 | a: { name: 'a', hp: 1 },
15 | b: { name: 'b', hp: 2 },
16 | c: { name: 'c', hp: 3 }
17 | });
18 |
19 | function useName(item) { return item.name; }
20 | });
21 |
22 | it('has mapValues() returning a list of the values of a map', function () {
23 | var map = {
24 | a: { name: 'a', hp: 1 },
25 | b: { name: 'b', hp: 2 },
26 | c: { name: 'c', hp: 3 }
27 | };
28 | expect(utils.mapValues(map)).toEqual([
29 | { name: 'a', hp: 1 },
30 | { name: 'b', hp: 2 },
31 | { name: 'c', hp: 3 }
32 | ]);
33 | });
34 |
35 | });
36 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/items.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function Item(name, effect) {
4 | this.name = name;
5 | this.effect = effect;
6 | }
7 |
8 | function Weapon(name, damage, extraEffect) {
9 | extraEffect = extraEffect || new Effect({});
10 | // Haz que Weapon sea subtipo de Item haciendo que llame al constructor de
11 | // de Item.
12 | }
13 | // Termina de implementar la herencia haciendo que la propiedad prototype de
14 | // Item sea el prototipo de Weapon.prototype y recuerda ajustar el constructor.
15 |
16 | function Scroll(name, cost, effect) {
17 | Item.call(this, name, effect);
18 | this.cost = cost;
19 | }
20 | Scroll.prototype = Object.create(Item.prototype);
21 | Scroll.prototype.constructor = Scroll;
22 |
23 | Scroll.prototype.canBeUsed = function (mp) {
24 | // El pergamino puede usarse si los puntos de maná son superiores o iguales
25 | // al coste del hechizo.
26 | };
27 |
28 | function Effect(variations) {
29 | // Copia las propiedades que se encuentran en variations como propiedades de
30 | // este objeto.
31 | }
32 |
33 | module.exports = {
34 | Item: Item,
35 | Weapon: Weapon,
36 | Scroll: Scroll,
37 | Effect: Effect
38 | };
39 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/Character.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var dice = require('./dice');
3 |
4 | function Character(name, features) {
5 | features = features || {};
6 | this.name = name;
7 | // Extrae del parámetro features cada característica y alamacénala en
8 | // una propiedad.
9 | this._mp = features.mp || 0;
10 | this.maxMp = features.maxMp || this._mp;
11 | }
12 |
13 | Character.prototype._immuneToEffect = ['name', 'weapon'];
14 |
15 | Character.prototype.isDead = function () {
16 | // Rellena el cuerpo de esta función
17 | };
18 |
19 | Character.prototype.applyEffect = function (effect, isAlly) {
20 | // Implementa las reglas de aplicación de efecto para modificar las
21 | // características del personaje. Recuerda devolver true o false según
22 | // si el efecto se ha aplicado o no.
23 | };
24 |
25 | Object.defineProperty(Character.prototype, 'mp', {
26 | get: function () {
27 | return this._mp;
28 | },
29 | set: function (newValue) {
30 | this._mp = Math.max(0, Math.min(newValue, this.maxMp));
31 | }
32 | });
33 |
34 | Object.defineProperty(Character.prototype, 'hp', {
35 | // Puedes usar la mísma ténica que antes para mantener el valor de hp en el
36 | // rango correcto.
37 | });
38 |
39 | // Puedes hacer algo similar a lo anterior para mantener la defensa entre 0 y
40 | // 100.
41 |
42 | module.exports = Character;
43 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/entities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var items = require('./items');
3 | var Character = require('./Character');
4 |
5 | var Effect = items.Effect;
6 |
7 |
8 | var lib = module.exports = {
9 | Item: items.Item,
10 | Weapon: items.Weapon,
11 | Scroll: items.Scroll,
12 | Effect: Effect,
13 | Character: Character,
14 |
15 | weapons: {
16 | get sword() {
17 | return new items.Weapon('sword', 25);
18 | },
19 | get wand() {
20 | return new items.Weapon('wand', 5);
21 | },
22 | // Implementa los colmillos y el pseudópodo
23 | },
24 |
25 | characters: {
26 |
27 | get heroTank() {
28 | return new Character('Tank', {
29 | initiative: 10,
30 | weapon: lib.weapons.sword,
31 | defense: 70,
32 | hp: 80,
33 | mp: 30
34 | });
35 | },
36 |
37 | // Implementa el mago
38 |
39 | get monsterSkeleton() {
40 | return new Character('skeleton', {
41 | initiative: 9,
42 | defense: 50,
43 | weapon: lib.weapons.sword,
44 | hp: 100,
45 | mp: 0
46 | });
47 | },
48 |
49 | // Implementa el limo y el murciélago
50 | },
51 |
52 | scrolls: {
53 |
54 | get health() {
55 | return new items.Scroll('health', 10, new Effect({ hp: 25 }));
56 | },
57 |
58 | // Implementa la bola de fuego
59 |
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/samplelib.js:
--------------------------------------------------------------------------------
1 | var entities = require('../src/entities');
2 |
3 | var Character = entities.Character;
4 | var Weapon = entities.Weapon;
5 | var Scroll = entities.Scroll;
6 | var Effect = entities.Effect;
7 |
8 | var lib = module.exports = {
9 | weapons: {
10 | get sword() { return new Weapon('Iron sword', 25); },
11 | get wand() { return new Weapon('Wood wand', 5, { mp: -5 }); },
12 | get claws() { return new Weapon('Claws', 15); }
13 | },
14 |
15 | characters: {
16 | get heroTank() {
17 | return new Character('Tank', {
18 | initiative: 10,
19 | weapon: lib.weapons.sword,
20 | defense: 70,
21 | hp: 80,
22 | maxHp: 80,
23 | mp: 0,
24 | maxMp: 0
25 | });
26 | },
27 |
28 | get heroWizard() {
29 | return new Character('Wizz', {
30 | initiative: 4,
31 | weapon: lib.weapons.wand,
32 | defense: 50,
33 | hp: 40,
34 | maxHp: 40,
35 | mp: 100,
36 | maxMp: 100
37 | });
38 | },
39 |
40 | get fastEnemy() {
41 | return new Character('Fasty', {
42 | initiative: 30,
43 | weapon: lib.weapons.claws,
44 | defense: 40,
45 | hp: 30,
46 | maxHp: 30,
47 | mp: 100,
48 | maxMp: 100
49 | });
50 | }
51 | },
52 |
53 | scrolls: {
54 | get health() {
55 | return new Scroll('Health', 10, new Effect({ hp: 25 }));
56 | },
57 | get fire() {
58 | return new Scroll('Fire', 30, new Effect({ hp: -25 }));
59 | }
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/Options.js:
--------------------------------------------------------------------------------
1 | describe('Options type', function () {
2 | 'use strict';
3 |
4 | var Options = require('../src/Options');
5 |
6 | var options;
7 |
8 | var dataFor = {
9 | itemA: {},
10 | itemB: {},
11 | itemC: {}
12 | };
13 |
14 | var items = {
15 | itemA: dataFor.itemA,
16 | itemB: dataFor.itemB,
17 | itemC: dataFor.itemC
18 | };
19 |
20 | beforeEach(function () {
21 | options = new Options(items);
22 | });
23 |
24 | it('allows the empty menu.', function () {
25 | options = new Options();
26 | expect(options.list()).toEqual([]);
27 | });
28 |
29 | it('list all the options.', function () {
30 | expect(options.list())
31 | .toEqual(jasmine.arrayContaining(Object.keys(items)));
32 | });
33 |
34 | it('recovers data associated to the menu item.', function () {
35 | expect(options.get('itemA')).toBe(dataFor['itemA']);
36 | });
37 |
38 | xit('emits an event when selecting an entry.', function (done) {
39 | var entryId = 'itemA';
40 | options.on('chose', function (id, data) {
41 | expect(id).toBe(entryId);
42 | expect(data).toBe(dataFor[entryId]);
43 | done();
44 | });
45 | options.select(entryId);
46 | });
47 |
48 | xit('emits an error event when the entry does not exist.', function (done) {
49 | var entryId = 'xxxx';
50 | options.on('choseError', function (reason, id) {
51 | expect(reason).toBe('option-does-not-exist');
52 | expect(id).toBe(entryId);
53 | done();
54 | });
55 | options.select(entryId);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-for-gamedev",
3 | "version": "1.0.0",
4 | "description": "Learning guide about JavaScript for game development",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "styles": "mkdirp ./es/styles && cpr styles/website.css es/styles/ -o",
9 | "serve:es": "npm run styles && gitbook serve ./es",
10 | "zip": "node scripts/generate-zips.js",
11 | "ebook:es": "mkdirp dist/es/ebook && npm run ebook:es:pdf && npm run ebook:es:epub",
12 | "ebook:es:pdf": "gitbook pdf ./es ./dist/es/ebook/js-for-gamedev.pdf",
13 | "ebook:es:epub": "gitbook epub ./es ./dist/es/ebook/js-for-gamedev.epub",
14 | "gitbook:es": "gitbook build ./es dist/es",
15 | "build:es": "npm run styles && npm run gitbook:es",
16 | "build:root": "mkdirp dist && cpr root dist -u -o",
17 | "build": "npm run zip && npm run build:root && npm run build:es",
18 | "clean": "rimraf dist ./es/styles es/**/*.zip",
19 | "deploy": "gh-pages -d dist"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/mozdevs/js-for-gamedev.git"
24 | },
25 | "keywords": [
26 | "learning",
27 | "tutorial",
28 | "javascript",
29 | "game",
30 | "dev"
31 | ],
32 | "license": "SEE LICENSE IN LICENSE.md",
33 | "bugs": {
34 | "url": "https://github.com/mozdevs/js-for-gamedev/issues"
35 | },
36 | "homepage": "https://github.com/mozdevs/js-for-gamedev#readme",
37 | "private": true,
38 | "dependencies": {
39 | "archiver": "^1.2.0",
40 | "cpr": "^2.0.0",
41 | "gh-pages": "^0.12.0",
42 | "gitbook-cli": "^2.3.0",
43 | "mkdirp": "^0.5.1",
44 | "rimraf": "^2.5.4"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/src/CharactersView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function CharactersView() {
4 | this._views = {};
5 | }
6 |
7 | CharactersView.prototype._visibleFeatures = [
8 | 'name',
9 | 'party',
10 | 'initiative',
11 | 'defense',
12 | 'hp',
13 | 'mp',
14 | 'maxHp',
15 | 'maxMp'
16 | ];
17 |
18 | CharactersView.prototype.all = function () {
19 | return Object.keys(this._views).reduce(function (copy, id) {
20 | copy[id] = this._views[id];
21 | return copy;
22 | }.bind(this), {});
23 | };
24 |
25 | CharactersView.prototype.allFrom = function (party) {
26 | return Object.keys(this._views).reduce(function (copy, id) {
27 | if (this._views[id].party === party) {
28 | copy[id] = this._views[id];
29 | }
30 | return copy;
31 | }.bind(this), {});
32 | };
33 |
34 | CharactersView.prototype.get = function (id) {
35 | return this._views[id] || null;
36 | };
37 |
38 | CharactersView.prototype.set = function (characters) {
39 | this._views = Object.keys(characters).reduce(function (views, id) {
40 | views[id] = this._getViewFor(characters[id]);
41 | return views;
42 | }.bind(this), {});
43 | };
44 |
45 | CharactersView.prototype._getViewFor = function (character) {
46 | var view = {};
47 | // Usa la lista de características visibles y Object.defineProperty() para
48 | // devolver un objeto de JavaScript con las características visibles pero
49 | // no modificables.
50 | Object.defineProperty(view, 'cada feature', {
51 | get: function () {
52 | // ¿Cómo sería este getter para reflejar la propiedad del personaje?
53 | },
54 | set: function (value) {
55 | // ¿Y este setter para ignorar cualquier acción?
56 | },
57 | enumerable: true
58 | });
59 | // Acuérdate de devolver el objeto.
60 | };
61 |
62 | module.exports = CharactersView;
63 |
--------------------------------------------------------------------------------
/es/03-javascript-en-el-navegador/03-practica/start-here/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Batalla RPG
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
RPG Battle
14 |
15 |
16 |
17 |
Heroes
18 |
19 |
20 |
21 |
Monsters
22 |
23 |
24 |
25 |
26 |
27 |
Fight!
28 |
29 |
30 |
31 |
36 |
37 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/es/03-javascript-en-el-navegador/03-practica/start-here/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | line-height: 1.2;
3 | font-family: monospace;
4 | font-size: 18px;
5 | }
6 |
7 | main {
8 | max-width: 900px;
9 | margin-left: auto;
10 | margin-right: auto;
11 | }
12 |
13 | body > h1 {
14 | text-align: center;
15 | }
16 |
17 | p, li {
18 | margin: 0.5em 0;
19 | }
20 |
21 | fieldset ul {
22 | padding-left: 0;
23 | }
24 |
25 | fieldset li {
26 | list-style-type: none;
27 | }
28 |
29 | button, input, select {
30 | font-family: monospace;
31 | font-size: 18px;
32 | }
33 |
34 | .parties {
35 | display: flex;
36 | justify-content: space-between;
37 | }
38 |
39 | .party {
40 | width: calc(50% - 2em);
41 | margin-left: 2em;
42 | }
43 |
44 | .party:first-child {
45 | margin-left: 0;
46 | }
47 |
48 | .party h1 {
49 | border-bottom: 1px solid #000;
50 | }
51 |
52 | .party li {
53 | list-style: none;
54 | text-transform: capitalize;
55 | position: relative;
56 | }
57 |
58 | .party ul {
59 | padding-left: 0;
60 | }
61 |
62 | .dead {
63 | text-decoration: line-through;
64 | }
65 |
66 | .active {
67 | background: rgba(255, 255, 0, 0.5);
68 | }
69 |
70 | .active::before {
71 | content: "► ";
72 | display: block;
73 | position: absolute;
74 | left: -1em;
75 | }
76 |
77 | .info {
78 | padding: 1em;
79 | background: #efefef;
80 | box-sizing: border-box;
81 | }
82 |
83 | .info strong {
84 | text-transform: capitalize;
85 | }
86 |
87 | .battle-menu .choices {
88 | text-transform: capitalize;
89 | padding-left: 0;
90 | }
91 |
92 | .battle-menu .choices li {
93 | list-style-type: none;
94 | }
95 |
96 | .battle-menu button {
97 | box-sizing: border-box;
98 | padding: 1em;
99 | }
100 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/TurnList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | xdescribe('The TurnList type', function () {
4 | var TurnList = require('../src/TurnList');
5 | var turnList;
6 | var characters;
7 |
8 | function FakeCharacter(party, inititative, isDead) {
9 | this.party = party;
10 | this.initiative = inititative;
11 | this._isDead = isDead;
12 | }
13 | FakeCharacter.prototype.isDead = function () {
14 | return this._isDead;
15 | };
16 |
17 | beforeEach(function () {
18 | characters = {
19 | a: new FakeCharacter('heroes', 1),
20 | b: new FakeCharacter('heroes', 5),
21 | c: new FakeCharacter('monsters', 10)
22 | };
23 |
24 | turnList = new TurnList();
25 | turnList.reset(characters);
26 | });
27 |
28 | it('accepts a set of characters and sort them by inititative.', function () {
29 | expect(turnList.turnNumber).toBe(0);
30 | expect(turnList.activeCharacterId).toBe(null);
31 | expect(turnList.list).toEqual(['c', 'b', 'a']);
32 | });
33 |
34 | it('accepts a set of characters and sort them by inititative.', function () {
35 | var turn = turnList.next();
36 |
37 | expect(turn.number).toBe(1);
38 | expect(turn.party).toBe(characters.c.party);
39 | expect(turn.activeCharacterId).toBe('c');
40 |
41 | expect(turnList.turnNumber).toBe(1);
42 | expect(turnList.activeCharacterId).toBe('c');
43 | });
44 |
45 | it('ignore all dead characters', function () {
46 | characters.c._isDead = true;
47 | characters.b._isDead = true;
48 | var turn = turnList.next();
49 |
50 | expect(turn.number).toBe(1);
51 | expect(turn.party).toBe(characters.a.party);
52 | expect(turn.activeCharacterId).toBe('a');
53 |
54 | expect(turnList.turnNumber).toBe(1);
55 | expect(turnList.activeCharacterId).toBe('a');
56 | });
57 |
58 | it('starts over when reaching the end of the list.', function () {
59 | turnList.next();
60 | turnList.next();
61 | turnList.next();
62 | var turn = turnList.next();
63 |
64 | expect(turn.number).toBe(4);
65 | expect(turn.party).toBe(characters.c.party);
66 | expect(turn.activeCharacterId).toBe('c');
67 |
68 | expect(turnList.turnNumber).toBe(4);
69 | expect(turnList.activeCharacterId).toBe('c');
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/es/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript para el desarrollo de videojuegos
2 |
3 | Esta es una guía de introducción a **JavaScript**, y está orientada al
4 | desarrollo de **videojuegos HTML5**.
5 |
6 | Esta basada en unos materiales que desarrollamos en una colaboración con la
7 | Universidad Complutense de Madrid para la asignatura de _Programación de
8 | videojuegos con lenguajes interpretados_. Puedes acceder a los materiales
9 | originales de la asignatura a través de
10 | [este repositorio en Github](https://github.com/clnznr/pvli2017).
11 |
12 | El código fuente de esta guía también está
13 | [publicado en Github](https://github.com/mozdevs/js-for-gamedev/). Si encuentras
14 | una errata o quieres sugerir algún cambio, por favor háznoslo saber
15 | [abriendo un ticket](https://github.com/mozdevs/js-for-gamedev/issues).
16 |
17 | ## Videojuegos en la Web
18 |
19 | La llegada de **HTML5** y sus tecnologías asociadas expandió enormemente las
20 | capacidades de la Web como **plataforma de videojuegos**. Hasta entonces, la
21 | mayoría de juegos web requerían un plugin externo –como Flash o Unity Player–,
22 | pero hoy ya no es necesario y los juegos HTML5 se ejecutan en el navegador de
23 | forma transparente.
24 |
25 | La Web nos ofrece **API** de gráficos 2D y 3D (esta última, basada en el estándar
26 | OpenGL ES), de reproducción y sintetización de audio, de acceso a múltiples
27 | métodos de entrada (_gamepads_, eventos de _touch_, giroscopios…), etc. En
28 | definitiva, todo lo que necesitamos para desarrollar videojuegos.
29 |
30 | Existen multitud de **motores y herramientas** para crear videojuegos HTML5.
31 | Algunos de los motores más populares, como Unity, Unreal o Game Maker, ya
32 | incluyen un exportador HTML5. También existen motores o frameworks específicos
33 | para la web, en los que podemos desarrollar con JavaScript, como Phaser o
34 | PlayCanvas.
35 |
36 | El objetivo de esta guía es proporcionar una base de conocimientos JavaScript
37 | para que puedas desarrollar videojuegos web utilizando librerías o motores web
38 | existentes.
39 |
40 | ## ¿A quién está dirigida esta guía?
41 |
42 | - A cualquiera con interés en el desarrollo de videojuegos y que ya tenga unos
43 | **conocimientos mínimos de programación** (cualquier lenguaje sirve, como Lua,
44 | C o Python): variables, bucles, funciones, condiciones, etc.
45 |
46 | - A programadores de videojuegos que quieran desarrollar videojuegos web con
47 | JavaScript.
48 |
49 | - A desarrolladores web que quieran aprender los fundamentos de la programación
50 | orientada a objetos con JavaScript.
51 |
52 | ---
53 |
54 | **Importante**:
55 |
56 | Se recomienda leer todos los artículos de una unidad, así como hacer los ejercicios guiados _antes_ de realizar la práctica propuesta.
57 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/OptionsStack.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var mockery = require('mockery');
4 |
5 | xdescribe('OptionsStack type', function () {
6 | var OptionsStack;
7 | var optionsStack;
8 |
9 | var MockOptions = jasmine.createSpy('MockOptions');
10 | MockOptions.prototype.select = function() {};
11 | MockOptions.prototype.list = function() {};
12 | MockOptions.prototype.get = function() {};
13 |
14 | beforeAll(function () {
15 | mockery.registerMock('./Options', MockOptions);
16 | mockery.enable({
17 | useCleanCache: true,
18 | warnOnUnregistered: false
19 | });
20 |
21 | OptionsStack = require('../src/OptionsStack');
22 | });
23 |
24 | afterAll(function () {
25 | mockery.disable();
26 | mockery.deregisterMock('./Options');
27 | });
28 |
29 | beforeEach(function () {
30 | spyOn(MockOptions.prototype, 'select');
31 | spyOn(MockOptions.prototype, 'list');
32 | spyOn(MockOptions.prototype, 'get');
33 | optionsStack = new OptionsStack();
34 | });
35 |
36 | it('adds an options group by assigning a group to current.', function () {
37 | var group = new MockOptions();
38 | optionsStack.current = group;
39 | expect(optionsStack.current).toBe(group);
40 | });
41 |
42 | it('adds an options group by assigning a object to current.', function () {
43 | var group = { a: 1, b: 2};
44 | optionsStack.current = group;
45 | expect(MockOptions).toHaveBeenCalledWith(group);
46 | expect(optionsStack.current).toEqual(jasmine.any(MockOptions));
47 | });
48 |
49 | it('returns to the previous options group when calling cancel().',
50 | function () {
51 | var group = new MockOptions();
52 | var group2 = new MockOptions();
53 | optionsStack.current = group;
54 | optionsStack.current = group2;
55 | expect(optionsStack.current).toBe(group2);
56 | optionsStack.cancel();
57 | expect(optionsStack.current).toBe(group);
58 | });
59 |
60 | it('proxies get() to the latest options group.', function () {
61 | var group = new MockOptions();
62 | optionsStack.current = group;
63 | optionsStack.get();
64 | expect(MockOptions.prototype.get).toHaveBeenCalled();
65 | });
66 |
67 | xit('proxies select() to the latest options group.', function () {
68 | var group = new MockOptions();
69 | optionsStack.current = group;
70 | optionsStack.select('x');
71 | expect(MockOptions.prototype.select).toHaveBeenCalledWith('x');
72 | });
73 |
74 | xit('proxies list() to the latest options group.', function () {
75 | var group = new MockOptions();
76 | optionsStack.current = group;
77 | optionsStack.list();
78 | expect(MockOptions.prototype.list).toHaveBeenCalled();
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/start-here/spec/CharacterView.js:
--------------------------------------------------------------------------------
1 | var samples = require('./samplelib');
2 |
3 | xdescribe('CharactesView type', function () {
4 | 'use strict';
5 |
6 | var CharactersView = require('../src/CharactersView');
7 |
8 | var charactersView;
9 |
10 | var heroTank = samples.characters.heroTank;
11 | var heroWizard = samples.characters.heroWizard;
12 |
13 | var visibleFeatures = [
14 | 'name',
15 | 'party',
16 | 'initiative',
17 | 'defense',
18 | 'hp',
19 | 'mp',
20 | 'maxHp',
21 | 'maxMp'
22 | ];
23 |
24 | beforeAll(function () {
25 | heroTank.party = 'teamA';
26 | heroWizard.party = 'teamB';
27 | });
28 |
29 | beforeEach(function () {
30 | charactersView = new CharactersView();
31 | charactersView.set({
32 | Tank: heroTank,
33 | Wizz: heroWizard
34 | });
35 | });
36 |
37 | it('shows only the visible features and includes id.', function () {
38 | var heroTankView = charactersView.get('Tank');
39 | var featuresCount = Object.keys(heroTankView).length ;
40 |
41 | expect(featuresCount).toBe(visibleFeatures.length);
42 | visibleFeatures.forEach(function (feature) {
43 | expect(heroTankView[feature]).toEqual(heroTank[feature]);
44 | });
45 | });
46 |
47 | it('list all characters.', function () {
48 | var heroTankView = charactersView.get('Tank');
49 | var heroWizardView = charactersView.get('Wizz');
50 | expect(charactersView.all())
51 | .toEqual({
52 | Tank: heroTankView,
53 | Wizz: heroWizardView
54 | });
55 | });
56 |
57 | it('list all characters by party', function () {
58 | var heroTankView = charactersView.get('Tank');
59 | var heroWizardView = charactersView.get('Wizz');
60 | expect(charactersView.allFrom('teamA'))
61 | .toEqual({
62 | Tank: heroTankView
63 | });
64 | expect(charactersView.allFrom('teamB'))
65 | .toEqual({
66 | Wizz: heroWizardView
67 | });
68 | });
69 |
70 | it('does not allow to modify character\'s features', function () {
71 | var heroTankView = charactersView.get('Tank');
72 |
73 | visibleFeatures.forEach(function (feature) {
74 | var expectedValue = heroTankView[feature];
75 | heroTankView[feature] = expectedValue + 1;
76 | expect(heroTankView[feature]).toEqual(expectedValue);
77 | });
78 | });
79 |
80 | it('does not modify the original character.', function () {
81 | var heroTankView = charactersView.get('Tank');
82 |
83 | var expectedValues = visibleFeatures.reduce(function (values, feature) {
84 | values[feature] = heroTank[feature];
85 | return values;
86 | }, {});
87 |
88 | visibleFeatures.forEach(function (feature) {
89 | heroTankView[feature] += 1;
90 | expect(heroTank[feature]).toEqual(expectedValues[feature]);
91 | });
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/es/01-intro/index.md:
--------------------------------------------------------------------------------
1 | # El entorno de trabajo
2 |
3 | ## La línea de comandos
4 |
5 | La línea de comandos es una aplicación para comunicarse con el sistema operativo
6 | a través de comandos escritos. Gran parte de las herramientas empleadas en el
7 | desarrollo web son aplicaciones de consola, es decir, aplicaciones cuya interfaz
8 | está pensada para usarse a través de una línea de comandos.
9 |
10 | En general, no necesitas instalar líneas de comandos de otros desarrolladores
11 | porque los sistemas operativos incluyen las suyas. Consulta una de estas guías
12 | acerca de cómo lanzar estas aplicaciones de acuerdo a tu sistema operativo:
13 |
14 | - En caso de trabajar sobre [Windows](http://www.howtogeek.com/235101/10-ways-to-open-the-command-prompt-in-windows-10/).
15 |
16 | - En caso de trabajar sobre [Mac OS](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line).
17 |
18 | - En caso de trabajar sobre [Linux](http://askubuntu.com/questions/183775/how-do-i-open-a-terminal).
19 |
20 | Conviene familiarizarse con los comandos que ofrecen los distintos sistemas
21 | operativos. Linux y Mac OS ofrecen un conjunto de utilidades muy similar basado
22 | en el estándar [POSIX](https://en.wikipedia.org/wiki/POSIX). Sin embargo,
23 | el conjunto de comandos de Windows es radicalmente distinto.
24 |
25 | De acuerdo a tu sistema operativo, utiliza una de las siguientes guías para
26 | aprender los comandos más normales en cada entorno:
27 |
28 | - Si tu sistema operativo es Windows, utiliza la guía de http://dosprompt.info/
29 |
30 | - Si es Mac OS o Linux, utiliza la guía de [_The Linux Information
31 | Project_](http://www.linfo.org/command_line_lesson_1.html)
32 |
33 | También puedes hacer que Windows ofrezca las mismas utilidades que Linux o Mac
34 | OS instalando [Cygwin](https://www.cygwin.com/).
35 |
36 | ## Node
37 |
38 | JavaScript es el lenguaje de programación de la Web y se ejecuta,
39 | principalmente, dentro de una máquina virtual integrada en un navegador.
40 | No obstante, este no es el único entorno en el que podemos utilizar JavaScript.
41 |
42 | Node es un intérprete JavaScript basado en V8, la máquina virtual del navegador
43 | Chrome, de Google. Node es ampliamente utilizado en el desarrollo de
44 | aplicaciones en el lado del servidor y será en Node que aprenderemos JavaScript
45 | para enfatizar su independencia del navegador.
46 |
47 | Para realizar los ejercicios y prácticas propuestos en esta guía, recomendamos
48 | [instalar cualquier versión de Node 6](https://nodejs.org/en/).
49 |
50 | ## El editor de texto
51 |
52 | Conviene tener en cuenta que para comenzar a programar en JavaScript sólo
53 | necesitas un editor de texto plano. Sin embargo, es recomendable elegir un
54 | editor con algunas características avanzadas que nos permita ser más
55 | productivos.
56 |
57 | **Nota**: es importante no confundir un editor de texto plano (como Notepad),
58 | con un procesador de texto (como Microsoft Word).
59 |
60 | El editor [Atom](https://atom.io/) es muy popular entre desarrolladores web. Es
61 | una buena opción por ser gratuito, de código abierto y extensible (y, además,
62 | ha sido programado con JavaScript).
63 |
64 | Si optas por Atom, te recomendamos que instales el siguiente _add-on_:
65 | [`linter-jshint`](https://github.com/AtomLinter/linter-jshint). Es un _linter_,
66 | un software que analizará tu código JavaScript. No sólo te corregirá el estilo
67 | (por ejemplo, que las líneas no superen cierto número de caracteres), sino que
68 | te avisará de posibles malas prácticas que podrían causar _bugs_ (como,
69 | por ejemplo, usar una variable sin haberla declarado antes).
70 |
71 |
72 | ### Editores de consola
73 |
74 | Es conveniente conocer al menos un editor de consola, puesto que no siempre
75 | trabajarás con entornos de escritorio. Hay varios muy buenos, entre ellos
76 | [Vim](http://www.vim.org/).
77 |
78 | De todas formas, si nunca has trabajado con un editor de consola, _recomendamos
79 | encarecidamente que uses Atom_ o cualquier otro editor con una interfaz visual.
80 | Es la forma más rápida de comenzar, puesto que la curva de aprendizaje de Vim
81 | es muy pronunciada.
82 |
83 | Si, pese a nuestras advertencias, optas por utilizar Vim, no olvides jugar a
84 | [Vim Adventures](http://vim-adventures.com/) y completar el tutorial de
85 | http://www.openvim.com, que te ayudarán a familiarizarte con el editor.
86 | Vim es también altamente configurable y existen varias guías sobre cómo mejorar
87 | la experiencia de programación.
88 |
89 | ## El navegador
90 |
91 | **Cualquier navegador moderno** sirve y, de hecho, recomendamos que instales
92 | varios para que puedas comprobar que el juego sea compatible entre navegadores.
93 |
94 | Debes aprender cómo activar las **herramientas de desarrollador** en tu
95 | navegador, ya que te permitirán depurar tu juego, analizar su rendimiento, etc.
96 | En esta guía mostraremos
97 | [Firefox Developer Edition](https://www.mozilla.org/firefox/developer/), que
98 | es una versión de Firefox especial para desarrolladores web e incluye
99 | herramientas y opciones no disponibles en la versión normal de Firefox.
100 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/GUIDE.md:
--------------------------------------------------------------------------------
1 | # Batalla RPG - Guía de la práctica
2 |
3 | La guía de la práctica te sugiere un orden para completar con éxito la
4 | implementación de la funcionalidad de la práctica. Prepárate, eso sí, para
5 | **leer mucho JavaScript** y ten a mano Google, la MDN y StackOverflow.
6 |
7 | ## 1. El modelo de datos
8 |
9 | En el juego existen muchos tipos de entidades, algunos relacionados y otros no.
10 |
11 | La especificación de tales entidades puede encontrarse en `spec/entities.js` y
12 | la implementación en `src/entities.js`, `src/Character.js` y `src/items.js`.
13 |
14 | ### Efectos
15 |
16 | Quizá el tipo más sencillo sea el tipo `Effect` en `src/items.js` y especificado
17 | en `spec/entities.js`. Comienza por aquí.
18 |
19 | ### Personajes
20 |
21 | Continúa activando los tests relacionados con el tipo `Character` y desactiva
22 | los demás.
23 |
24 | A continuación abre `src/Character.js` e implementa las partes que faltan.
25 |
26 | ### Elementos
27 |
28 | Continúa con el módulo `src/items.js` activando paulatinamente las _suites_ para
29 | los tipos `Item`, `Weapon` y `Scroll` que encontrarás en `spec/entities.js`.
30 |
31 | ### Entidades por defecto
32 |
33 | Tienes que crear algunos personajes, armas y pergaminos por defecto para que
34 | otras prácticas puedan usarlos. La _suite_ _built-in entities_ en
35 | `spec/entities.js` incluye todas las expectativas de estas entidades.
36 |
37 | Ve al archivo `src/entities.js` y completa las que falten. Fíjate que las
38 | propiedades son _getters_ para que cada acceso a las propiedades te devuelvan
39 | un nuevo personaje.
40 |
41 | ## 2. La lista de turnos
42 |
43 | Esta es fácil. La especificación se encuentra en `spec/TurnList.js` y la
44 | implementación en `src/TurnList.js`. Tan sólo rellena los huecos. Es pura
45 | algoritmia. Quizá tengas que mirarte cómo funciona la función `Array.sort()`
46 | para no implementarte tu propia función de ordenamiento.
47 |
48 | ## 3. La vista del personaje
49 |
50 | La vista del personaje es una representación de sólo lectura de las estadísticas
51 | del mismo. Su especificación está en `spec/CharactersView.js` y su
52 | implementación en `src/CharactersView.js`. Puedes continuar por ahí.
53 |
54 | ## 4. El grupo de opciones
55 |
56 | El grupo de opciones representa las opciones que se pueden elegir en un momento
57 | dado. La especificación está en `spec/Options.js` y la implementación,
58 | casi completa, en `src/Options.js`. Fíjate cómo el tipo `Options`
59 | extiende `EventEmitter`. Tu misión será implementar el método `.select()` para
60 | que al llamarlo se emita un evento acorde con la especificación.
61 |
62 | ## 5. La pila de opciones
63 |
64 | Un RPG se compone normalmente de varios menús apilados. Por ejemplo, el menú
65 | de acciones da paso al menú de hechizos que da paso al menú de objetivos. En
66 | cualquier momento podemos regresar al menú anterior. La pila de opciones en
67 | `src/OptionsStack.js` y especificada en `spec/OptionsStack.js` refleja este
68 | comportamiento.
69 |
70 | La API es la misma que el grupo de opciones pero los métodos realmente sólo
71 | se deben redirigir al último menú apilado. Fíjate en cómo se apilan y desapilan
72 | menús nuevos.
73 |
74 | ## 6. Utilidades
75 |
76 | Tranqui. No tienes que hacer nada aquí, tan sólo ten en cuenta que tienes el
77 | módulo `src/utils.js` donde puedes colocar más utilidades si encuentras que
78 | andas repitiendo el mismo código en muchas partes. Te aconsejo que escribas
79 | algún test. Puedes inspirarte en los que ya hay en `spec/utils.js`.
80 |
81 | ## 7. La batalla
82 |
83 | Has llegado al plato fuerte de la práctica. Hasta aquí era todo preparar los
84 | tipos en los que se apoya el tipo `Battle`. Ahora tendrás que implementar
85 | la máquina de estados que controla las acciones de batalla: defender, atacar
86 | y lanzar un hechizo.
87 |
88 | Fíjate que los combatientes, sus armas y los hechizos **no son los que vienen
89 | por defecto en `src/entities.js`** sino los que se encuentran en
90 | `spec/samplelib.js`.
91 |
92 | La especificación de la batalla está en `spec/Battle.js` y la implementación en
93 | `src/Battle.js`. Para abordar esta implementación con éxito es necesario que
94 | todos los tests hasta ahora pasen.
95 |
96 | En esta parte de la práctica no hay recomendaciones sobre qué tests activar
97 | primero. Tendrás que experimentar.
98 |
99 | Repasa bien el código, muchos de los ejercicios consisten en dar las
100 | implementaciones de **funciones auxiliares**. Es el caso de:
101 | + `assignParty`
102 | + `useUniqueNames`
103 | + `isAlive`
104 | + `getCommonParty`
105 |
106 | Fíjate entonces en la función `_showAction` que hará que las acciones de
107 | batalla estén disponibles en el atributo `options`.
108 |
109 | Ahora concéntrate en las acciones. La implementación de `_defend` está casi
110 | hecha. Sólo tendrás que completar las funciones `_improveDefense` y
111 | `_restoreDefense` para el cálculo de la defensa mejorada.
112 |
113 | La acción _defend_ rellena la estructura `this._action` con el nombre de la
114 | acción, los identificadores del personaje activo y del objetivo, el efecto
115 | y la nueva defensa. Todos menos la defensa son necesarios para poder llamar
116 | a la función `_executeAction` que ejecutará la acción e informará del
117 | resultado.
118 |
119 | Durante el proceso de implementación de las acciones, tendrás que implementar
120 | también `_showTargets` y `_showScrolls` de manera similar, de acuerdo a la
121 | especificación y al [enunciado](index.md).
122 |
123 | ## 8. Calidad del código
124 | Cuando termines y todos tus tests estén en verde habrás terminado la práctica.
125 |
126 | Rotula la rama con un _tag_ o cambia de rama antes de mejorar la calidad del
127 | código.
128 |
129 | Lee ahora los errores de estilo que el comando de tests pueda proporcionar y
130 | pasa los tests cada vez que realices una modificación para asegurarte que no
131 | has roto nada.
132 |
133 | ## Fin
134 |
135 | ¡Enhorabuena! Has completado la práctica.
136 |
--------------------------------------------------------------------------------
/es/02-javascript/02-practica/TDD.md:
--------------------------------------------------------------------------------
1 | # TDD: Desarrollo dirigido por tests
2 |
3 | Practicando TDD, iremos escribiendo código de forma que los tests pasen. Los
4 | tests vienen dados pero están desactivados. La [guía de la práctica](
5 | ./GUIDE.md) recomienda en qué orden activar los tests para completar la práctica
6 | poco a poco.
7 |
8 | ### Tests y suites
9 |
10 | En esta práctica usamos [**Jasmine**](http://jasmine.github.io) como framework
11 | para tests. En Jasmine escribimos suites y tests. Las suites se pueden anidar
12 | y pueden llevar código de inicialización. En general, la API de Jasmine es muy
13 | clara y no necesita mayor explicación. De todas formas, aquí tienes un ejemplo:
14 |
15 | ```js
16 | describe('Las suites en Jasmine', function () {
17 |
18 | describe('pueden anidarse', function () {
19 |
20 | it('y encierran tests con expectativas', function () {
21 | expect(2 + 2).toBe(4);
22 | });
23 |
24 | });
25 |
26 | });
27 | ```
28 |
29 | Se llama test a un fragmento de código que pone a prueba una funcionalidad
30 | específica. El test puede pasar o fallar. En caso de fallo, la consola mostrará
31 | por qué ha fallado en la forma de una traza:
32 |
33 | ```js
34 | 15.2) Expected 'b' to be 'c'.
35 | Error: Expected 'b' to be 'c'.
36 | at stack (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1640:17)
37 | at buildExpectationResult (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1610:14)
38 | at Spec.expectationResultFactory (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:655:18)
39 | at Spec.addExpectationResult (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:342:34)
40 | at Expectation.addExpectationResult (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:599:21)
41 | at Expectation.toBe (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1564:12)
42 | at Object. (/Users/salva/workspace/pvli2017-rpg-battle/spec/TurnList.js:39:36)
43 | at attemptSync (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1950:24)
44 | at QueueRunner.run (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1938:9)
45 | at QueueRunner.execute (/Users/salva/workspace/pvli2017-rpg-battle/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1923:10)
46 | ```
47 |
48 | La traza contiene el fallo y dónde se ha producido en el conjunto de llamadas
49 | desde la más reciente hasta la más vieja. A veces los fallos son producto de
50 | implementaciones que no cumplen las expectativas, otras veces serán fallos en
51 | tiempo de ejecución y otras serán fallos de sintaxis.
52 |
53 | Acostúmbrate a fallar y a encontrar en la traza el punto exacto del código que
54 | está bajo tu control para solucionarlo. Para ello busca las carpetas `spec` y
55 | `src` entre la traza. El primer número tras la ruta es la línea del fallo.
56 |
57 | ### Activando y desactivando tests
58 |
59 | Los tests y las suites pueden desactivarse añadiendo el prefijo `x`. Por
60 | ejemplo:
61 |
62 | ```js
63 | describe('Las suites en Jasmine', function () {
64 |
65 | describe('pueden anidarse', function () {
66 |
67 | it('y encierran tests con expectativas', function () {
68 | expect(2 + 2).toBe(4);
69 | });
70 |
71 | xit('este test está desactivado', function () {
72 |
73 | });
74 |
75 | });
76 |
77 | xdescribe('la suite y todos sus tests están desactivados.', function () {
78 |
79 | });
80 |
81 | });
82 | ```
83 |
84 | Los tests desactivados no comprueban las expectativas pero Jasmine te
85 | informa de que están desactivados.
86 |
87 | ### El ciclo de desarrollo
88 |
89 | Cuando estés desarrollando, es conveniente que pases los test a menudo por dos
90 | motivos:
91 |
92 | - Comprobar que avanzas.
93 | - Comprobar que no has roto nada.
94 |
95 | Para ello puedes ejecutar el comando:
96 |
97 | ```
98 | $ npm run-script watch
99 | ```
100 |
101 | Esta tarea monitoriza los cambios en los archivos de las carpetas `spec` y
102 | `src` y cuando detecte un cambio, lanzara todos los tests.
103 |
104 | A veces, el error es tan estrepitoso que rompe la monitorización. En tal caso
105 | tendrás que reintroducir el comando manualmente.
106 |
107 | Cada vez que realices una modificación y los tests pasen, haz un commit nuevo.
108 |
109 | ### Depurando tests asíncronos
110 |
111 | Algunos tests son asíncronos y pueden producir _timeouts_. En general un
112 | _timeout_ no es un resultado positivo. El problema de los _timeouts_ es que
113 | pueden ralentizar toda la suite así que la recomendación en estos casos es
114 | afrontarlos uno a uno, desactivando el resto y activándolos poco a poco.
115 |
116 | Reconocerás un test asíncrono porque lleva un parámetro `done` como en el
117 | ejemplo:
118 |
119 | ```js
120 | var EventEmitter = require('events').EventEmitter;
121 |
122 | describe('EventEmitter', function () {
123 |
124 | it('emite eventos arbitrarios', function (done) {
125 | var ee = new EventEmitter();
126 | ee.on('turn', function(turn) {
127 | expect(turn.number).toBe(1);
128 | done();
129 | });
130 | ee.emit('turn', { number: 1 });
131 | });
132 |
133 | });
134 | ```
135 |
136 | ## Estrategia general para la depuración
137 |
138 | Es muy recomendable que mantengas una rama estable donde todos los tests pasen
139 | y los que no estén desactivados. Cuando te embarques en la tarea de hacer que
140 | un test pase, crea una rama para esa tarea y cuando termines mézclala con la
141 | rama estable.
142 |
143 | Cuando encuentres un error, intenta seguir los siguientes pasos:
144 | 1. Desactiva los tests asíncronos que estén tardando demasiado. **Necesitas un
145 | ciclo de desarrollo rápido.**
146 | 2. **¡¡Lee el error!!**.
147 | 3. Busca en la traza el lugar donde se original el error:
148 | 1. Si es un fallo en una expectativa, localiza el punto de entrada en
149 | tu código.
150 | 2. Deja trazas con `console.log()` inspeccionando el estado de tus objetos.
151 | 4. Salva y relanza los tests a menudo.
152 |
--------------------------------------------------------------------------------
/es/03-javascript-en-el-navegador/03-practica/index.md:
--------------------------------------------------------------------------------
1 | # Cliente web para batallas RPG
2 |
3 | A continuación se expone la práctica propuesta para esta unidad. Tras leer este enunciado, se recomienda _encarecidamente_ consultar la **[guía de la práctica](guia.md)** para su realización.
4 |
5 | ## Enunciado
6 |
7 | La práctica consiste en implementar un cliente visual (en este caso, **una página web**) para el juego de batallas de la práctica de la unidad anterior.
8 |
9 | 
10 |
11 | Como punto de partida, se ha provisto de un href="start-here.zip" target="_blank">esqueleto de proyecto con los siguientes archivos:
12 |
13 | - `index.html`: código HTML de partida
14 | - `styles.css`: hoja de estilo
15 | - `js/main.js`: el archivo de partida JavaScript
16 | - `js/rpg.js`: un archivo con el código de la librería de batallas de la práctica. También puedes usar tu propio código (consulta la sección de _Adaptación del código de la práctica anterior_).
17 |
18 | En este código inicial se incluye ya implementado lo siguiente:
19 |
20 | - Carga desde el archivo HTML de los recursos JavaScript y CSS.
21 |
22 | - Esqueleto HTML con una interfaz ya hecha. Puedes modificar este HTML para añadir más cosas, o cambiar elementos de la UI que no te convenzan, pero no es obligatorio.
23 |
24 | - Creación de una instancia de `Battle`, así como el setup de las _parties_ y la subscripción a los eventos más relevantes. La información de dichos eventos se imprime por consola (lo cual puedes eliminar/modificar a tu gusto).
25 |
26 | Hay que implementar las siguientes _features_:
27 |
28 | - Mostrar los personajes de ambas _parties_, con sus ID's, puntos de vida y de maná.
29 |
30 | - Marcar qué personaje está seleccionado, cambiando su estilo o añadiendo un carácter especial (p.ej: `*`).
31 |
32 | - Marcar qué personajes están muertos, cambiando su estilo o añadiendo un carácter especial (p.ej: `✝`).
33 |
34 | - Implementar el menú de batalla con sus siguientes estados: selección de acción, selección de objetivo y selección de hechizo.
35 |
36 | - Mostrar información de qué ha pasado cada turno (p.ej `Bat attacked Wizz, but missed`).
37 |
38 | - Mostrar un mensaje al final de la batalla indicando cuál es el bando ganador.
39 |
40 | Para implementar estas _features_ básicas, es recomendable seguir el procedimiento marcado por **la [guía](GUIDE.md) de la práctica**.
41 |
42 | Otras características opcionales que se podrían implementar, serían:
43 |
44 | - En el menú de selección de objetivo, mostrar en un color diferente los personajes de cada _party_.
45 |
46 | - Al terminar la batalla, mostrar un botón o enlace para empezar una nueva (esto se puede hacer simplemente recargando la página).
47 |
48 | - Crear la composición de una o ambas _parties_ de manera aleatoria.
49 |
50 | ## Adaptación del código de la práctica anterior
51 |
52 | En la versión actual de JavaScript no hay ningún mecanismo para gestionar módulos (por ejemplo, usando la función `require` como en Node). Es por esto que no podemos utilizar ni `require` ni `module.exports`.
53 |
54 | Además, ciertas partes que forman parte de la librería estándar de Node, como el módulo `events` para implementar eventos, no forman parte del estándar JavaScript.
55 |
56 | Hay una herramienta, [**Browserify**](http://browserify.org/) que nos permite transformar módulos de Node –con sus dependencias– en código que funciona en el browser. También incluye **polyfills**.
57 |
58 | ### Instrucciones
59 |
60 | #### Opción A: usar el código propio
61 |
62 | Si has acabado la práctica anterior, puedes utilizar ese código en esta. Sigue los pasos que hay a continuación para adaptar ese código de Node a código que puedas ejecutar en el navegador.
63 |
64 | 1. Como vamos a necesitar dos módulos, `Battle` y `entities` (junto a sus dependencias), tenemos que crear un archivo "raíz" con esos dos. Crea en la raíz del directorio de la práctica anterior un archivo `export.js` con el siguiente contenido:
65 |
66 | ```javascript
67 | module.exports = {
68 | "Battle": require('./src/Battle.js'),
69 | "entities": require('./src/entities.js')
70 | };
71 | ```
72 |
73 | 2. De nuevo en la raíz del directorio de la práctica anterior, instala Browserify con npm:
74 |
75 | ```
76 | npm install --save-dev browserify
77 | ```
78 |
79 | 3. Comprueba que el archivo `package.json` se ha modificado y que ahora aparece Browserify listado dentro de `devDependencies`. Por ejemplo:
80 |
81 | ```json
82 | "devDependencies": {
83 | "browserify": "^13.1.0"
84 | }
85 | ```
86 |
87 | 4. Edita `package.json` para añadir un comando de script más, que ejecutará Browserify:
88 |
89 | ```json
90 | "scripts": {
91 | "bundle": "browserify export.js --standalone RPG > rpg.js"
92 | }
93 | ```
94 |
95 | 5. Ejecuta dicho comando, que generará un archivo `rpg.js` en el raíz de ese directorio.
96 |
97 | ```bash
98 | npm run bundle
99 | ```
100 |
101 | 6. Ahora puedes copiar `rpg.js` al directorio `js` de la práctica 2. Cuando se cargue el archivo con una etiqueta `