License text

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER 
 * Copyright  2008, 2010 Oracle and/or its affiliates.  All rights reserved. 
 * Use is subject to license terms.
 * 
 * This file is available and licensed under the following license:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 
 *   * Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer. 
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *   * Neither the name of Oracle Corporation nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software 
 *     without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

    

package dukeman;

import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;

// @author Pavel Porvatov

def BONUS_SCORE = [200, 400, 800, 1600];

def STATE_GET_READY = 1;
def STATE_PLAY = 2;
def STATE_GAME_OVER = 3;

public class Level {
    public-init var level: LevelData;

    def duke = Duke { }

    // Direction correspondent to keyboard
    def keyboardDirection = Direction { };

    def enemies = [
        Enemy {
            type: Enemy.TYPE_0
        },
        Enemy {
            type: Enemy.TYPE_1
        },
        Enemy {
            type: Enemy.TYPE_2
        },
        Enemy {
            type: Enemy.TYPE_3
        },
    ];

    def levelCaption = Text {
        translateX: Config.SCREEN_WIDTH / 8
        translateY: Config.GAME_FIELD_Y / 6
        fill: Color.BLACK
        font: Font {
            name: "Bitstream Vera Sans Bold"
            size: if (Config.IS_MOBILE) 12 else 20
        }
        textOrigin: TextOrigin.TOP
        content: "Level {Main.mainFrame.state}"
    }

    def scoreCaption = Text {
        translateX: bind levelCaption.translateX
        translateY: Config.GAME_FIELD_Y / 6 + Config.GAME_FIELD_Y / 4
        fill: bind levelCaption.fill
        font: bind levelCaption.font
        textOrigin: TextOrigin.TOP
        content: "YOUR SCORE"
    }

    def scoreValue = Text {
        translateX: Config.SCREEN_WIDTH * 2 / 3
        translateY: bind scoreCaption.translateY
        fill: bind levelCaption.fill
        font: bind levelCaption.font
        textOrigin: TextOrigin.TOP
    }

    def highScoreCaption = Text {
        translateX: bind levelCaption.translateX
        translateY:  Config.GAME_FIELD_Y / 6 + Config.GAME_FIELD_Y / 2
        fill: bind levelCaption.fill
        font: bind levelCaption.font
        textOrigin: TextOrigin.TOP
        content: "HIGH SCORE"
    }

    def highScoreValue = Text {
        translateX: bind scoreValue.translateX
        translateY: bind highScoreCaption.translateY
        fill: bind levelCaption.fill
        font: bind levelCaption.font
        textOrigin: TextOrigin.TOP
        content: "{Main.mainFrame.highScore}"
    }

    def message: ImageView = ImageView {
        translateX: bind Config.GAME_FIELD_X + (Config.GAME_FIELD_WIDTH -
            message.image.width) / 2
        translateY: bind Config.GAME_FIELD_Y + 
            (level.enemyHome.y + LevelData.ENEMY_HOME_HEIGHT) * Config.CELL_SIZE +
            (Config.CELL_SIZE - message.image.height) / 2
    }

    var state: Integer = STATE_GET_READY;

    var stateTime: Duration;

    var animatedDots: Dot[];

    var bonusIndex: Integer;
    
    var bonuses: Bonus[];

    var dotCount: Integer;

    var lives: ImageView[];

    def group = Group {
        translateX: Config.GAME_FIELD_X
        translateY: Config.GAME_FIELD_Y
    };

    public function start() {
        // Fill scene content
        def background = ImageView {
            focusTraversable: true
            image: Config.images[Config.IMAGE_BACKGROUND0 +
                (level.levelNumber - 1) mod 3]

            onKeyPressed: function( e: KeyEvent ):Void {
                if (state == STATE_GAME_OVER) {
                    Main.mainFrame.state = 0;
                } else if (state == STATE_PLAY) {
                    updateKeyboardDirection(e.code, true)
                }
            }
        }

        var content: Node[];

        insert [
            background,

            levelCaption,
            scoreCaption,
            scoreValue,
            highScoreCaption,
            highScoreValue
        ] into content;

        // Maze
        for (node in level.maze) {
            node.translateX += Config.GAME_FIELD_X;
            node.translateY += Config.GAME_FIELD_Y;

            insert node into content;
        }

        for (node in level.dots) {
            node.translateX += Config.GAME_FIELD_X;
            node.translateY += Config.GAME_FIELD_Y;

            insert node into content;

            if (not node.small) {
                insert node into animatedDots;
            }
        }

        group.content = [
            enemies,
            duke
        ];

        insert group into content;
        insert message into content;

        Main.mainFrame.scene.content = content;
        
        dotCount = sizeof level.dots;

        timeline.play();
        background.requestFocus();
        
        updateScore(0);
        updateLives();
        startGame();
    }

    public function stop() {
        timeline.stop();
    }

    function startGame() {
        duke.initialize(level);

        keyboardDirection.offsetX = 0;
        keyboardDirection.offsetY = 0;

        for (enemy in enemies) {
            enemy.initialize(level);
        }

        for (bonus in bonuses) {
            delete bonus from bonuses;
            delete bonus from group.content;
        }

        state = STATE_GET_READY;
        stateTime = 3s;
        message.image = Config.images[Config.IMAGE_GET_READY];
        message.opacity = 1;
        message.visible = true
    }

    function gameOver() {
        state = STATE_GAME_OVER;
        message.image = Config.images[Config.IMAGE_GAME_OVER];
        message.opacity = 1;
        message.visible = true;

        // Update high scores
        if (Main.mainFrame.score > Main.mainFrame.highScore) {
            Main.mainFrame.highScore = Main.mainFrame.score
        }
    }

    // Updates keyboard state
    function updateKeyboardDirection(code: KeyCode, pressed: Boolean) {
        var offsetX: Integer;
        var offsetY: Integer;

        if (code == KeyCode.VK_UP) {
            offsetY = -1;
        } else if (code == KeyCode.VK_RIGHT) {
            offsetX = 1;
        } else if (code == KeyCode.VK_DOWN) {
            offsetY = 1;
        } else if (code == KeyCode.VK_LEFT) {
            offsetX = -1;
        } else {
            return
        }

        if (pressed) {
            keyboardDirection.offsetX = offsetX;
            keyboardDirection.offsetY = offsetY;
        } else {
            if (offsetX != 0) {
                keyboardDirection.offsetX = 0;
            }

            if (offsetY != 0) {
                keyboardDirection.offsetY = 0;
            }
        }
    }

    function updateScore(inc: Integer) {
        var oldScore = Main.mainFrame.score;

        Main.mainFrame.score += inc;
        scoreValue.content = "{Main.mainFrame.score}";

        if (oldScore / 10000 != Main.mainFrame.score / 10000) {
            Main.mainFrame.lifeCount++;

            updateLives();
        }
    }
    
    function updateLives() {
        // Remove lives
        while (sizeof lives > Main.mainFrame.lifeCount) {
            var life = lives[sizeof lives - 1];

            delete life from lives;
            delete life from Main.mainFrame.scene.content;
        }

        // Add lives (but no more than 9)
        for (i in [sizeof lives..<java.lang.Math.min(Main.mainFrame.lifeCount, 9)]) {
            var image = Config.images[Config.IMAGE_DUKE_LIVES];

            var life = ImageView {
                image: Config.images[Config.IMAGE_DUKE_LIVES]
                translateX: Config.GAME_FIELD_X + image.width * i
                translateY: Config.SCREEN_HEIGHT - Config.BOTTOM_AREA_HEIGHT +
                    (Config.BOTTOM_AREA_HEIGHT - image.height) / 2
            }

            insert life into lives;
            insert life into Main.mainFrame.scene.content;
        }
    }
    
    def timeline = Timeline {
        repeatCount: Timeline.INDEFINITE

        keyFrames : [
            KeyFrame {
                time: Config.ANIMATION_TIME
                action: function () {
                    // Animate dots
                    for (dot in animatedDots) {
                        dot.animate();
                    }

                    // Animate bonuses
                    for (bonus in bonuses) {
                        if (not bonus.animate()) {
                            delete bonus from bonuses;
                            delete bonus from group.content;
                        }
                    }

                    if (state == STATE_GET_READY) {
                        stateTime -= Config.ANIMATION_TIME;

                        if (stateTime < 1s) {
                            message.opacity = stateTime.toMillis() / 1000;
                        }

                        if (stateTime < 0s) {
                            state = STATE_PLAY;

                            message.visible = false;
                        }

                        return;
                    }

                    if (state == STATE_GAME_OVER) {
                        return;
                    }
                    
                    // Process duke
                    duke.move(level, keyboardDirection);

                    // Collect dot
                    def index: Integer = (duke.translateX + Config.CELL_SIZE / 2 as Integer) / Config.CELL_SIZE +
                        (duke.translateY + Config.CELL_SIZE / 2 as Integer) / Config.CELL_SIZE * LevelData.WIDTH;

                    var node = level.levelData.elementAt(index) as Node;

                    if (node instanceof Dot) {
                        level.levelData.setElementAt(null, index);

                        if ((node as Dot).small) {
                            updateScore(10);
                        } else {
                            bonusIndex = 0;

                            updateScore(50);

                            // Mark enemies as safe
                            for (enemy in enemies) {
                                enemy.setState(Enemy.STATE_SAFE);
                            }
                        }

                        node.visible = false;
                        delete node as Dot from animatedDots;

                        dotCount--;

                        if (dotCount == 0) {
                            // Go to the next level
                            Main.mainFrame.state++;

                            return;
                        }
                    }

                    // Move enemy
                    for (enemy in enemies) {
                        enemy.move(level, duke);

                        if (duke.isIntersected(enemy)) {
                            if (enemy.state == Enemy.STATE_HUNTING) {
                                // Duke is catched
                                Main.mainFrame.lifeCount--;

                                if (Main.mainFrame.lifeCount < 0) {
                                    gameOver();
                                } else {
                                    updateLives();

                                    startGame();
                                }
                                
                                return
                            } else if (enemy.state == Enemy.STATE_SAFE) {
                                enemy.setState(Enemy.STATE_DEFEAT);

                                updateScore(BONUS_SCORE[bonusIndex]);

                                var bonus = Bonus {
                                    type: Config.IMAGE_BONUS_200 + bonusIndex
                                    translateX: enemy.translateX
                                    translateY: enemy.translateY
                                }

                                insert bonus into bonuses;
                                insert bonus before group.content[0];

                                bonusIndex++
                            }
                        }
                    }
                }
            }
        ]
    }
}