Code Sketch


Worm Game
By: Lalit Pant [2]
/* WORM GAME
 * copyright (c) 2011 by Bjorn Regnell and Lalit Pant
 * Given under the GNU License:
 * http://www.gnu.org/copyleft/gpl.html
 */

import scala.collection._
import math.round

abstract class Direction
case object North extends Direction
case object South extends Direction
case object East extends Direction
case object West extends Direction

abstract class GameState
case object Begin extends GameState
case object Ready extends GameState
case object Play extends GameState
case object GameOver extends GameState

val S = Staging

class Worm(
    val myNum : Int,
    val startingPoint : Point,
    val col: Color,
    val name : String,
    val initDir: Direction,
    val initDelta: Double,
    var energy: Int = 1000
) {
    var x = startingPoint.x
    var y = startingPoint.y
    var dir = initDir
    var tpath = S.path(x, 0)
    
    initPen(initDelta)
    
    def initPen(penThickness:Double) {
        tpath.setPenColor(col)
        tpath.setPenThickness(penThickness)        
    }
    
    def restart(d:Double) {
        x = startingPoint.x; y = startingPoint.y
        dir = initDir
        tpath.hide
        tpath = S.path(x, 0)
        initPen(d)
    }
    
    def moveby(dx: Double, dy: Double) {
        x = x + dx; y = y + dy
        tpath.lineTo(x, y)
    }
    
    def move(delta:Double) {
        dir match {
            case West => moveby(-delta, 0)
            case East => moveby(delta, 0)
            case South => moveby(0, -delta)
            case North => moveby(0, delta)
        }        
    }  
    
    def isInside(xmin:Double,ymin:Double,xmax:Double,ymax:Double):Boolean = {
        if (x>xmin && x<xmax && y>ymin && y<ymax) true
        else false
    }
    
    def lose(p:Int) = {energy -= p}
}

def scale(xy:Double, d:Double):Int = (xy/(d-d/10)).round.toInt


class Trail(val delta:Double){
    var track = mutable.Map.empty[(Int,Int),Int]
    def trace(x:Double,y:Double,mark:Int) {
        //require(mark > 0)
        track += (scale(x, delta),scale(y, delta)) -> mark
    }
    def whoWasHere(x:Double,y:Double):Int = 
        track.getOrElse((scale(x, delta),scale(y, delta)),-1)
}

class WormGame(val maxSpeed:Double = 20, 
               val startEnergy:Int = 1000,
               val startSpeed:Double = 3,
               val speedUpFactor:Double = 0.1
) {
    def hasCrashed(w:Worm):Boolean = 
        (trail.whoWasHere(w.x, w.y) > 0) || (!w.isInside(xmin,ymin,xmax,ymax))        

    def nextSpeed(d:Double):Double = math.min(d+d*speedUpFactor, maxSpeed)
    def incEnergyCut(nrg:Int, d:Double):Int = nrg + d.round.toInt + 1
    
    def drawPoints(p:Double,here:Point, c:Color) = {
        val r1 = S.rectangle(here.x, here.y, 20, (300*p/startEnergy) max 0.1)
        r1.setPenColor(green)
        r1.setFillColor(c)
        val r2 = S.rectangle(here.x+2.0, here.y+((300*p/startEnergy) max 0.0), 
                             16.0,  (((299.0*(1-p/startEnergy)) max 0.1) min 300))                    
        r2.setPenColor(black)
        r2.setFillColor(black)        
    }
    
    def setTextAbove(t:String) = {textAbove.setContent(t)}
    def setTextBelow(t:String) = {textBelow.setContent(t)}
    
    def exitGame {
        S.stop
        gameStopped = true
        setTextAbove(w1.name+" HAS "+w1.energy + "   " +
                  w2.name+" HAS "+w2.energy)
        setTextBelow("EXIT GAME! BYE BYE ...")
        val msg = if (w1.energy == w2.energy) "NO" else
            if (w1.energy < w2.energy) w1.name else w2.name
        val t = S.text(msg + " TURKEY :-(", -105, 0)
        t.scale(2)
        println(w1.name+" HAS "+w1.energy)
        println(w2.name+" HAS "+w2.energy)
        println("EXIT GAME BYEBYE")       
    }
    
    var delta : Double = startSpeed
    var trail = new Trail(delta)   
    val xmax = 150.0
    val ymax = 150.0
    val xmin = -150.0
    val ymin = -150.0
    
    var gameState : GameState = Begin
    var gameStopped = false
    def changeState(nextState : GameState) {
        gameState = nextState
        println(gameState)
    }
    var energyCut = 0
    
    S.clear
    //S.background(black)  //this gives java.lang.IllegalArgumentException-WHY?
    S.setFillColor(black)
    S.setPenColor(green)
    S.rectangle(xmin, ymin, xmax-xmin, ymax-ymin)
    S.noFill()
    
    val rectAbove = S.rectangle(xmin,ymax,300,20)
    rectAbove.setFillColor(black)
    val textAbove = S.text("CLICK ON GAME THEN PRESS SPACE",xmin+5,ymax+20)
    
    val rectBelow = S.rectangle(xmin,ymin-20,300,20)
    rectBelow.setFillColor(black)
    val textBelow = S.text("BE READY FOR THE WORM GAME!",xmin+2,ymin)
    
    var w1 = new Worm(1,S.point(xmax, 0), color(0,100,255), 
                      "BLUE", West, delta, startEnergy)
    var w2 = new Worm(2,S.point(xmin, 0), color(255,0,10), 
                      "RED", East, delta, startEnergy) 
       
    onKeyPress { k =>
        k match {
            case Kc.VK_LEFT => w1.dir = West
            case Kc.VK_RIGHT => w1.dir = East
            case Kc.VK_UP => w1.dir = North
            case Kc.VK_DOWN => w1.dir = South
            case Kc.VK_A => w2.dir = West
            case Kc.VK_D => w2.dir = East
            case Kc.VK_W => w2.dir = North
            case Kc.VK_S => w2.dir = South
            case Kc.VK_SPACE => {
                    if (!gameStopped) gameState match {
                        case Ready => {
                                setTextAbove("PLAY")
                                println("SPACE pressed in Ready!")
                                setTextBelow("DON'T CRASH") //??? why not shown second time
                                drawPoints(w1.energy, S.point(-172,-150),w1.col)
                                drawPoints(w2.energy, S.point(152,-150),w2.col)
                                changeState(Play)
                            }
                        case GameOver => {
                                println("SPACE pressed in GameOver!")
                                setTextAbove("PRESS SPACE WHEN READY")
                                delta = nextSpeed(delta)
                                trail = new Trail(delta)
                                print("SPEED =" + delta)
                                w1.restart(delta)
                                w2.restart(delta)
                                energyCut = 0
                                changeState(Ready)
                            }
                        case _ =>
                    }
                    else setTextAbove("PLEASE RESTART GAME TO PLAY AGAIN!")
                }
            case Kc.VK_ESCAPE => exitGame
            case _ => 
        }
    }
    
    S.animate {
        gameState match {
            case Begin => { 
                    println(gameState)
                    changeState(Ready)
                }
            case Ready => { 
                    // onKeyPress wait for space
                }
            case Play => { 
                    trail.trace(w1.x, w1.y, w1.myNum)
                    trail.trace(w2.x, w2.y, w2.myNum)
                    //print(trail.track)
                    w1.move(delta); w2.move(delta)
                    if (scale(w1.x, delta) == scale(w2.x, delta) && 
                               scale(w1.y, delta) == scale(w2.y, delta)) { //this sometimes gives rounding errors - should be checking distance instead
                        setTextBelow("HEAD CRASH - BOTH LOSE "+energyCut)
                        w1.lose(energyCut)
                        drawPoints(w1.energy, S.point(-172,-150),w1.col)
                        w2.lose(energyCut)
                        drawPoints(w2.energy, S.point(152,-150),w2.col) 
                        if (w1.energy <= 0 || w2.energy <= 0) exitGame
                        changeState(GameOver)
                    } else if (hasCrashed(w1)) {
                        println(w1.name+" has crashed")
                        setTextBelow(w1.name+" LOSE "+energyCut+" ENERGY UNITS")
                        w1.lose(energyCut)
                        drawPoints(w1.energy, S.point(-172,-150),w1.col)
                        if (w1.energy <= 0) exitGame
                        changeState(GameOver)
                    } else if (hasCrashed(w2)) {
                        println(w2.name+" has crashed")
                        setTextBelow(w2.name+" LOSE "+energyCut+" ENERGY UNITS")
                        w2.lose(energyCut)
                        drawPoints(w2.energy, S.point(152,-150),w2.col) 
                        if (w2.energy <= 0) exitGame
                        changeState(GameOver)
                    } 
                    energyCut = incEnergyCut(energyCut, delta)
                }
            case GameOver => { 
                }
        }
    }
}

val wg = new WormGame(startSpeed = 3, 
                      maxSpeed = 20, 
                      startEnergy = 5000,
                      speedUpFactor = 0.15
)