Monday, January 21, 2008

Scala Pong 4K - Source Code Now

I was afraid to post my source code for Scala Pong 4K because I'm afraid it's not a good example of Scala programming, nor of 4K game programming, nor of programming in general. Learning a new language while staying under 4K with limited time for working on it provides less than ideal results. And I purposely avoided many Scala features because I was afraid of the weight it would add. (4K is really small.) For instance, I avoided arrays. Part of why I chose something as simple as pong (and Pong is really trademarked, so I guess that was a bad name choice). But it makes sense why people might want to see the source anyway. Also, finally, here is the Scala license which says I'm supposed to show their terms and copyright (though it would be nice if ProGuard could have stripped it all out, just to make it smaller).

All that out of the way, here's the source code which I kept all in one file "Game.scala". And it looks like it get visually chopped off on the right, so you might need to copy and paste it into an editor to see everything:

package sp4k

import java.awt._
import java.awt.event._
import javax.swing._

object Game {

// Don't extend a big class with an 'object'. That makes tons of static methods.

def main(args: Array[String]) {
SwingUtilities.invokeAndWait(new GamePanel)
}

}

class GamePanel extends JPanel with Runnable with ActionListener {

// Closure vars are smaller than member vars because no accessor methods are generated.
// Except that then it uses Scala's IntRef class, so a new dependency.
// So back to member vars.

var y1 = 200
var v1 = 0
var y2 = 200
var v2 = 0

var xb = 0
var yb = 0
var vx = 0
var vy = 0

def actionPerformed(e: ActionEvent) {
xb += vx
yb += vy
if (xb < -5 || xb > 605) {
initBall()
} else if (xb > 573) {
if (yb > y2 - 40 && yb < y2 + 40) {
xb = 573
vx = -vx - 1
}
} else if (xb < 27) {
if (yb > y1 - 40 && yb < y1 + 40) {
xb = 27
vx = -vx + 1
}
}
if (yb > 375) {
yb = 375
vy = -vy
} else if (yb < 25) {
yb = 25
vy = -vy
}
y1 += v1
if (y1 < 60) {
y1 = 60
} else if (y1 > 340) {
y1 = 340
}
y2 += v2
if (y2 < 60) {
y2 = 60
} else if (y2 > 340) {
y2 = 340
}
repaint()
}

def bar(g: Graphics2D, x1: Int, y: Int, x2: Int) {
g.setPaint(new GradientPaint(x1, 0, new Color(40, 40, 0), x2, 0, new Color(160, 160, 0)))
g.fillRect(java.lang.Math min(x1, x2), y - 40, java.lang.Math abs(x2 - x1), 80)
}

def initBall() {
xb = 300
yb = 200
vx = (if (java.lang.Math.random() < 0.5) -1 else 1) * ((10 * java.lang.Math.random()).asInstanceOf[Int] + 5)
vy = (if (java.lang.Math.random() < 0.5) -1 else 1) * 10
}

override def paint(graphics: Graphics) {
val g = graphics.asInstanceOf[Graphics2D]
g.setPaint(new Color(0, 0, 0))
g.fillRect(0, 0, 600, 400)
g.setPaint(new GradientPaint(0, 0, new Color(40, 40, 40), 0, 20, new Color(80, 80, 80)))
g.fillRect(0, 0, 600, 20)
g.setPaint(new GradientPaint(0, 380, new Color(80, 80, 80), 0, 400, new Color(40, 40, 40)))
g.fillRect(0, 380, 600, 20)
bar(g, 2, y1, 22)
bar(g, 598, y2, 578)
g.setPaint(new Color(255, 255, 255))
g.fillOval(xb - 5, yb - 5, 10, 10)
}

def run() {
initBall()
setPreferredSize(new Dimension(600, 400))
setFocusable(true)
addKeyListener(new KeyAdapter() {
override def keyPressed(e: KeyEvent) {
val code = e.getKeyCode()
if (code == KeyEvent.VK_UP) {
v2 = -5
} else if (code == KeyEvent.VK_DOWN) {
v2 = 5
} else if (code == KeyEvent.VK_W) {
v1 = -5
} else if (code == KeyEvent.VK_S) {
v1 = 5
}
}
override def keyReleased(e: KeyEvent) {
val code = e getKeyCode()
if (code == KeyEvent.VK_UP) {
if (v2 == -5) v2 = 0
} else if (code == KeyEvent.VK_DOWN) {
if (v2 == 5) v2 = 0
} else if (code == KeyEvent.VK_W) {
if (v1 == -5) v1 = 0
} else if (code == KeyEvent.VK_S) {
if (v1 == 5) v1 = 0
}
}
})
val frame = new JFrame("Scala Pong 4K - Use W/S and Up/Down")
frame.setLayout(new BorderLayout())
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setResizable(false)
frame.add(this, BorderLayout.CENTER)
frame.pack()
frame.setVisible(true)
new Timer(50, this).start()
}

}


And I just used ProGuard from the GUI (remember I was just hacking this), but somewhere along the way, I asked it to spit out the configuration:

-injars bin
-injars lib/scala-library.jar
-outjars output

-libraryjars /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/classes.jar

-optimizationpasses 3
-allowaccessmodification


# Keep - Applications. Keep all application classes, along with their 'main'
# methods.
-keepclasseswithmembers public class sp4k.* {
public static void main(java.lang.String[]);
}

# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
native ;
}

# Remove - System method calls. Remove all invocations of System
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.System {
public static long currentTimeMillis();


And it actually keeps on going with lots of method lists of that "assumenosideeffects" stuff. Hmm. So I'll stop listing it here.

3 comments:

  1. Of course, I should have started a post today with respect to Martin Luther King, Jr. Day. When my kids get older, it would be good to participate in the various public service opportunities generally presented.

    ReplyDelete
  2. Thanks for posting the source! It was very illuminating to see how you did it in so few lines of code. I've been reading some other Scala blogs in the meantime, so I was pleasantly surprised to see that I could totally follow your implementation.

    ReplyDelete
  3. I think the line count would be about the same in Java, though some of the lines would have to be longer. I think 4K is a tough limit to showcase Scala (and I don't really have the experience yet to showcase much). Still, glad to hear that it was easy to follow. And right now, I'm hoping to learn Scala better. Maybe if I try again next year for the same kind of stunt, I'll do a better job.

    ReplyDelete