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.
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.
ReplyDeleteThanks 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.
ReplyDeleteI 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