/* 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
)