source: pycodeshooter/trunk/shooter/system.js @ 130

Revision 130, 19.6 KB checked in by atzm, 12 years ago (diff)
  • add power-up items
  • Property svn:keywords set to Id
Line 
1/* -*- coding: utf-8 -*-
2 *
3 * Copyright (C) 2010 by Atzm WATANABE <atzm@atzm.org>
4 *
5 *  This program is free software; you can redistribute it and/or modify it
6 *  under the terms of the GNU General Public License (version 2) as
7 *  published by the Free Software Foundation.  It is distributed in the
8 *  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
9 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10 *  PURPOSE.  See the GNU General Public License for more details.
11 *
12 * $Id$
13 *
14 */
15
16System = {
17    "screen": {
18        "canvas": null,
19        "ctx":    null,
20        "width":  0,
21        "height": 0
22    },
23    "sound":            {},
24    "message":          null,
25    "enemyImages":      new Array(),
26    "enemies":          new Array(),
27    "players":          new Array(),
28    "score":            {},
29    "stage":            -1,
30    "backgroundObject": new Array(),
31    "deathPieces":      new Array(),
32    "items":            new Array(),
33    "mainIntervalId":   0
34};
35
36
37/*
38 *  Tiun Tiun Utilities
39 */
40var DeathPiece = function(sizes, colors, x, y, dir, speed) {
41    var that = new LinerBullet(sizes[0], colors[0], null,
42                               System.screen.width, System.screen.height,
43                               x, y, dir, speed);
44
45    var sizeIdx  = -1;
46    var colorIdx = -1;
47
48    that.getSize = function() {
49        if (++sizeIdx >= sizes.length)
50            sizeIdx = 0;
51        return sizes[sizeIdx];
52    };
53
54    that.getColor = function() {
55        if (++colorIdx >= colors.length)
56            colorIdx = 0;
57        return colors[colorIdx];
58    };
59
60    return that;
61};
62
63function addDeathPieces(x, y, sizes, colors, speed, way) {
64    if (way % 2)
65        way++;
66
67    var pieces = new Array();
68    var angle  = 0;
69    var delta  = 2 / way;
70
71    for(var i = 0; i < way; i++) {
72        pieces.push(new DeathPiece(sizes, colors, x, y, angle * Math.PI, speed));
73        angle += delta;
74    }
75
76    System.deathPieces.push(pieces);
77}
78
79function updateDeathPieces(ctx, width, height) {
80    var newObjs = new Array();
81
82    for (var i = 0; i < System.deathPieces.length; i++) {
83        var pieces    = System.deathPieces[i];
84        var newPieces = new Array();
85
86        for (var k = 0; k < pieces.length; k++) {
87            pieces[k].next();
88            pieces[k].draw(ctx);
89            if (!pieces[k].vanished(width, height))
90                newPieces.push(pieces[k]);
91        }
92
93        if (newPieces.length)
94            newObjs.push(newPieces);
95    }
96
97    System.deathPieces = newObjs;
98}
99
100
101/*
102 *  Item Utilities
103 */
104function addItem(x, y, angle, size, color, speed, attrs) {
105    var frame = {"style": "rect", "color": attrs.color, "width": size * 2, "height": size * 2};
106    var item  = new YExtendBullet(size, color, frame,
107                                  System.screen.width, System.screen.height,
108                                  x, y, angle * Math.PI, speed);
109    item.attrs = attrs;
110    System.items.push(item);
111}
112
113function updateItems(ctx, width, height, troopers) {
114    var newObjs = new Array();
115
116  ITEMLOOP:
117    for (var i = 0; i < System.items.length; i++) {
118        var item = System.items[i];
119
120        item.next();
121        item.draw(ctx, function(bullet, ctx) {
122            drawString(
123                ctx, "source-over", item.attrs.symbol,
124                bullet.x, bullet.y,
125                item.attrs.color, item.size + "pt monospace", "center"
126            );
127        });
128
129        for (var j = 0; j < troopers.length; j++) {
130            var trooper = troopers[j];
131
132            if (touch(trooper.x, trooper.y, trooper.size, item.x, item.y, item.size)) {
133                switch (item.attrs.type) {
134                case "score":
135                    playSound("se_item_score");
136                    if (System.score[trooper.name] !== undefined)
137                        System.score[trooper.name] += item.attrs.quantity;
138                    break;
139                case "shot":
140                    playSound("se_item_shot");
141                    for (var k = 0; k < trooper.barrages.length; k++) {
142                        trooper.barrages[k].way += item.attrs.quantity;
143                        if (trooper.barrages[k].way >= 10)
144                            trooper.barrages[k].way = 10;
145                    }
146                    break;
147                case "bomb":
148                    playSound("se_item_bomb");
149                    trooper.numBombs += item.attrs.quantity;
150                    break;
151                case "life":
152                    playSound("se_item_life");
153                    trooper.life += item.attrs.quantity;
154                    break;
155                }
156
157                continue ITEMLOOP;
158            }
159        }
160
161        if (!item.vanished(width, height))
162            newObjs.push(item);
163    }
164
165    System.items = newObjs;
166}
167
168
169/*
170 *  Utility Functions
171 */
172function setMessage(elem, msg) {
173    if (elem)
174        elem.innerHTML = msg;
175}
176
177function addMessage(elem, msg) {
178    if (elem)
179        elem.innerHTML += msg;
180}
181
182function updateBackground(ctx, width, height, size, color, max) {
183    if (System.backgroundObject.length < max) {
184        var x = Math.ceil(Math.random() * width);
185        var s = Math.ceil(Math.random() * 5);
186        System.backgroundObject.push(
187            new LinerBullet(size, color, null,
188                            System.screen.width, System.screen.height,
189                            x, 0, 0.5 * Math.PI, s));
190    }
191
192    var newObjs = new Array();
193
194    for (var i = 0; i < System.backgroundObject.length; i++) {
195        System.backgroundObject[i].next();
196        System.backgroundObject[i].draw(ctx);
197        if (!System.backgroundObject[i].vanished(width, height))
198            newObjs.push(System.backgroundObject[i]);
199    }
200
201    System.backgroundObject = newObjs;
202}
203
204function drawScreen(ctx, op, style, width, height) {
205    var c = ctx.globalCompositeOperation;
206    ctx.globalCompositeOperation = op;
207    ctx.beginPath();
208    ctx.fillStyle = style;
209    ctx.fillRect(0, 0, width, height);
210    ctx.fill();
211    ctx.closePath();
212    ctx.globalCompositeOperation = c;
213}
214
215function drawLifeGauge(ctx, op, trooper, x, y, width) {
216    var length = trooper.life;
217
218    if (length > width - 20)
219        length = width - 20;
220
221    var c = ctx.globalCompositeOperation;
222    ctx.globalCompositeOperation = op;
223    ctx.beginPath();
224    ctx.fillStyle = trooper.color;
225    ctx.fillRect(x, y, length, 10);
226    ctx.fill();
227    ctx.closePath();
228    ctx.globalCompositeOperation = c;
229
230    drawString(ctx, op, trooper.life, x + 2, y + 8, trooper.color,
231               "6pt monospace", "left");
232}
233
234function drawString(ctx, op, string, x, y, color, font, align) {
235    var a = ctx.textAlign;
236    var f = ctx.font;
237    var c = ctx.globalCompositeOperation;
238    ctx.globalCompositeOperation = op;
239    ctx.beginPath();
240    ctx.textAlign = align;
241    ctx.fillStyle = color;
242    ctx.font      = font;
243    ctx.fillText(string, x, y);
244    ctx.fill();
245    ctx.textAlign = a;
246    ctx.font      = f;
247    ctx.closePath();
248    ctx.globalCompositeOperation = c;
249}
250
251function updateTrooper(trooper, enemyKey) {
252    trooper.update(System[enemyKey]);
253
254    var aliveEnemies = new Array();
255    for (var i = 0; i < System[enemyKey].length; i++) {
256        var enemy = System[enemyKey][i];
257
258        if (enemy.isDead()) {
259            if (System.score[trooper.name] !== undefined) {
260                System.score[trooper.name] += enemy.maxLife * 100;
261            }
262        }
263        else {
264            aliveEnemies.push(enemy);
265        }
266    }
267    System[enemyKey] = aliveEnemies;
268
269    trooper.draw(System.screen.ctx);
270}
271
272function getStageNumber() {
273    return System.stage;
274}
275
276function switchStage(base) {
277    var scores   = Object.keys(System.score);
278    var sum      = 0;
279    var stages   = new Array();
280    var score    = 0;
281    var stage    = 1;
282    var switched = false;
283
284    for (var i = 0; i < scores.length; i++) {
285        sum += System.score[scores[i]];
286    }
287
288    for (var name in System.sound) {
289        if (!name.match(/^bgm_stage/)) continue;
290        stages.push(name);
291    }
292
293    score        = Math.round(sum / scores.length);
294    stage        = Math.floor((score % (stages.length * base)) / base) + 1;
295    switched     = System.stage != stage;
296    System.stage = stage;
297
298    return switched;
299}
300
301function switchBgm(stage) {
302    for (var name in System.sound) {
303        if (!name.match(/^bgm_stage/))
304            continue;
305        if (("bgm_stage" + stage) == name)
306            playSound(name);
307        else
308            pauseSound(name, true);
309    }
310}
311
312function toggleSound(val) {
313    for (var name in System.sound)
314        System.sound[name].muted = !val;
315}
316
317function registerSound(name, audio) {
318    System.sound[name] = audio;
319}
320
321function playSound(name) {
322    if (System.sound[name])
323        System.sound[name].play();
324}
325
326function pauseSound(name, stop) {
327    if (System.sound[name]) {
328        System.sound[name].pause();
329        if (stop)
330            System.sound[name].currentTime = 0;
331    }
332}
333
334function getEnemiesOnScreen() {
335    return System.enemies.length;
336}
337
338function addEnemyImage(image) {
339    System.enemyImages.push(image);
340}
341
342function addEnemy(enemyData) {
343    var actList = EnemyActionLists[enemyData.mtime % EnemyActionLists.length];
344    var shot    = EnemyShots[enemyData.hitpoint % EnemyShots.length];
345    var numAct  = enemyData.agility       % (EnemyActions.length  - 1) + 1;
346    var numBlt  = enemyData.skills.length % (EnemyBullets.length  - 1) + 1;
347    var numBrrg = enemyData.skills.length % (EnemyBarrages.length - 1) + 1;
348    var acts    = new Array();
349    var brrgs   = new Array();
350
351    var bulletWay         = Math.ceil(enemyData.concentration / 10);
352    var bulletInterval    = Math.round(50 * 1 / Math.log(enemyData.skillpoint + 0.1));
353    var bulletSize        = Math.round(Math.log(enemyData.luck + 1));
354    var bulletFrameWidth  = (bulletSize + 5) * 2;
355    var bulletFrameHeight = (bulletSize + 5) * 4;
356    var bulletSpeed       = enemyData.strength / 15;
357
358    bulletSpeed = Math.log(bulletSpeed < 1.5 ? 1.5 : bulletSpeed);
359
360    for (var i = 0; i < numAct; i++) {
361        var idx = (enemyData.agility + i) % EnemyActions.length;
362        acts.push(new EnemyActions[idx](new shot()));
363    }
364
365    for (var i = 0; i < numBrrg; i++) {
366        var idx     = (enemyData.skillpoint + i * (enemyData.skills.length + 1)) % EnemyBarrages.length;
367        var brrgCls = EnemyBarrages[idx];
368        var frameColor;
369
370        switch(idx % 3) {
371        case 0:
372            frameColor = "rgba(128,32,32,0.7)";
373            break;
374        case 1:
375            frameColor = "rgba(32,128,32,0.7)";
376            break;
377        default:
378            frameColor = "rgba(64,64,128,0.7)";
379        }
380
381        for (var k = 0; k < numBlt; k++) {
382            var iidx = (enemyData.skills.length + i + k) % EnemyBullets.length;
383            brrgs.push(
384                new brrgCls(
385                    EnemyBullets[iidx],
386                    bulletSize,
387                    "#FF3",
388                    {"style": "rect", "color": frameColor,
389                     "width": bulletFrameWidth, "height": bulletFrameHeight},
390                    bulletInterval,
391                    bulletSpeed,
392                    bulletWay
393                )
394            );
395        }
396    }
397
398    var size  = Math.ceil((System.screen.width / 2) * (1 / enemyData.defense));
399    var enemy = new Trooper(
400        enemyData.name,
401        new actList(acts),
402        System.enemyImages[enemyData.hitpoint % System.enemyImages.length],
403        size,
404        size,
405        "#F33",
406        "#F33",
407        Math.floor(Math.random() * System.screen.width),
408        Math.floor(Math.random() * (System.screen.height / 4)),
409        System.screen.width,
410        System.screen.height,
411        Math.floor(enemyData.hitpoint / 25) + 1,
412        Math.log(enemyData.agility + 0.1) * 3,
413        0,
414        ["rgba(255,0,0,0.3)", "rgba(0,0,255,0.3)"],
415        brrgs
416    );
417
418    enemy.registerCallback("damaged", function() {
419        if (enemy.isDead()) {
420            playSound("se_destroy");
421
422            addDeathPieces(
423                enemy.x, enemy.y,
424                [6, 8, 10], ["#F55", "#FAA"], 3, 8
425            );
426
427            var itemAttr;
428            var itemRand = Math.floor(Math.random() * 100);
429            if (itemRand < 40) {
430                // nothing
431            }
432            else if (itemRand < 75) {
433                itemAttr = {"color": "#AAF", "symbol": "S", "type": "score", "quantity": enemy.maxLife * 200};
434            }
435            else if (itemRand < 95) {
436                itemAttr = {"color": "#FAA", "symbol": "P", "type": "shot", "quantity": 1};
437            }
438            else if (itemRand < 98) {
439                itemAttr = {"color": "#FFA", "symbol": "B", "type": "bomb", "quantity": 1};
440            }
441            else {
442                itemAttr = {"color": "#AFA", "symbol": "L", "type": "life", "quantity": 1};
443            }
444
445            if (itemAttr)
446                addItem(enemy.x, enemy.y, 0.5, 5, "#AFA", 1, itemAttr);
447        }
448        else {
449            playSound("se_damage");
450        }
451    });
452
453    System.enemies.push(enemy);
454}
455
456
457/*
458 *  Main loop
459 */
460function mainLoop() {
461    // clear screen
462    drawScreen(
463        System.screen.ctx,
464        "source-over",
465        "rgba(8,8,8,0.8)",
466        System.screen.width,
467        System.screen.height
468    );
469
470    // update background objects
471    updateBackground(
472        System.screen.ctx,
473        System.screen.width,
474        System.screen.height,
475        1, "#CAF", 10
476    );
477
478    // switch stage
479    if (switchStage(50000))
480        switchBgm(getStageNumber());
481
482    // draw stage number
483    drawString(
484        System.screen.ctx,
485        "source-over",
486        "STAGE " + getStageNumber(),
487        System.screen.width - 10,
488        15,
489        "#ACF", "9pt monospace", "right"
490    );
491
492    // draw score
493    var scoreNames = Object.keys(System.score).sort();
494    for (var i = 0; i < scoreNames.length; i++) {
495        var name = scoreNames[i];
496        drawString(
497            System.screen.ctx,
498            "source-over",
499            name + " SCORE " + System.score[name],
500            (System.screen.width - 10),
501            i * 16 + 30,
502            "#ACF", "9pt monospace", "right"
503        );
504    }
505
506    // update/draw items
507    updateItems(System.screen.ctx,
508                System.screen.width,
509                System.screen.height,
510                System.players);
511
512    // update/draw troopers
513    for (var i = 0; i < System.players.length; i++) {
514        var player = System.players[i];
515
516        updateTrooper(player, "enemies");
517
518        drawLifeGauge(
519            System.screen.ctx,
520            "lighter",
521            player,
522            10,
523            (System.screen.height - 20) - (i * 33),
524            System.screen.width
525        );
526
527        drawString(
528            System.screen.ctx,
529            "source-over",
530            player.name,
531            10,
532            (System.screen.height - 23) - (i * 33),
533            "#ACF", "9pt monospace", "left"
534        );
535
536        drawString(
537            System.screen.ctx,
538            "source-over",
539            "BOMB " + player.numBombs,
540            (System.screen.width - 10),
541            (System.screen.height - 23) - (i * 33),
542            "#ACF", "9pt monospace", "right"
543        );
544    }
545
546    // update/draw enemies
547    for (var i = 0; i < System.enemies.length; i++) {
548        var enemy = System.enemies[i];
549
550        updateTrooper(enemy, "players");
551
552        drawLifeGauge(
553            System.screen.ctx,
554            "lighter",
555            enemy, 10, i * 33 + 10,
556            System.screen.width
557        );
558
559        drawString(
560            System.screen.ctx,
561            "source-over",
562            enemy.name, 10, i * 33 + 33,
563            "#FCA", "9pt monospace", "left"
564        );
565    }
566
567    updateDeathPieces(System.screen.ctx,
568                      System.screen.width,
569                      System.screen.height);
570
571    if (!System.players.length) {
572        drawString(
573            System.screen.ctx, "source-over",
574            "GAME OVER",
575            System.screen.width / 2,
576            System.screen.height / 2,
577            "#ACF", "24pt monospace", "center"
578        );
579    }
580}
581
582
583/*
584 *  Initializer
585 */
586function initGame(canvas, msg, playerData) {
587    System.screen.canvas = canvas;
588    System.message       = msg;
589    System.screen.ctx    = System.screen.canvas.getContext("2d");
590    System.screen.width  = System.screen.canvas.width;
591    System.screen.height = System.screen.canvas.height;
592    System.gameOver      = false;
593
594    System.screen.ctx.globalCompositeOperation = "lighter";
595
596    if (System.mainIntervalId) {
597        clearInterval(System.mainIntervalId);
598        System.mainIntervalId   = 0;
599        System.players          = new Array();
600        System.enemies          = new Array();
601        System.backgroundObject = new Array();
602        System.deathPieces      = new Array();
603    }
604
605    var trooper = new Trooper(
606        playerData.name,
607        new ActionList([new ManualAction(new ManualShot())]),
608        playerData.image,
609        playerData.size,
610        playerData.hitsize,
611        "#33F",
612        "#F33",
613        System.screen.width / 2,
614        System.screen.height - System.screen.height / 7,
615        System.screen.width,
616        System.screen.height,
617        playerData.hitpoint,
618        playerData.speed,
619        playerData.numbombs,
620        ["rgba(255,0,0,0.3)", "rgba(0,0,255,0.3)"],
621        [new LinerBarrage(YExtendBullet,
622                          playerData.shotsize,
623                          "rgba(64,64,128,0.7)",
624                          null,
625                          playerData.shotinterval,
626                          playerData.shotspeed,
627                          playerData.shotlevel,
628                          -0.5),
629         new LinerBarrage(LinerBullet,
630                          playerData.shotsize,
631                          "rgba(64,64,128,0.7)",
632                          null,
633                          playerData.shotinterval,
634                          playerData.shotspeed,
635                          playerData.shotlevel,
636                          -0.5)]
637    );
638    trooper.registerCallback("addBomb", function() { playSound("se_bomb"); });
639    trooper.registerCallback("damaged", function() {
640        playSound("se_destroy");
641
642        addDeathPieces(
643            trooper.x, trooper.y,
644            [6, 8, 10], ["#55F", "#AAF"], 3, 8
645        );
646
647        for (var i = 0; i < System.enemies.length; i++) {
648            System.enemies[i].clearBullet();
649        }
650
651        trooper.x = System.screen.width / 2;
652        trooper.y = System.screen.height - System.screen.height / 7;
653        trooper.numBombs = playerData.numbombs;
654        trooper.barrages = [
655            new LinerBarrage(YExtendBullet,
656                             playerData.shotsize,
657                             "rgba(64,64,128,0.7)",
658                             null,
659                             playerData.shotinterval,
660                             playerData.shotspeed,
661                             playerData.shotlevel,
662                             -0.5),
663            new LinerBarrage(LinerBullet,
664                             playerData.shotsize,
665                             "rgba(64,64,128,0.7)",
666                             null,
667                             playerData.shotinterval,
668                             playerData.shotspeed,
669                             playerData.shotlevel,
670                             -0.5)
671        ];
672    });
673
674    System.players.push(trooper);
675
676    for (var i = 0; i < System.players.length; i++) {
677        System.score[System.players[i].name] = 0;
678    }
679
680    drawScreen(
681        System.screen.ctx,
682        "source-over",
683        "rgba(0,0,0,1)",
684        System.screen.width,
685        System.screen.height
686    );
687
688    document.onkeydown  = function (ev) { setKeyDown(ev.keyCode); };
689    document.onkeyup    = function (ev) { setKeyUp(ev.keyCode); };
690    document.onkeypress = function (ev) { setKeyPress(ev.charCode); };
691
692    System.mainIntervalId = setInterval(mainLoop, 20);
693}
Note: See TracBrowser for help on using the repository browser.