Rather redundant at this point but here is another framework. It has focus listening with a titlescreen and pause state also text rendering and sub-image drawing. Demo with 4 running at once here
public class TinyGame extends Applet implements FocusListener, Runnable, MouseListener {
public static final int FRAMES_PER_SEC = 30;
public enum GameState {
TITLE_SCREEN,
PAUSED,
RUNNING
}
private TinyScreen screen;
private Thread gameThread;
private boolean done;
private GameState state;
// test game
private Random rand = new Random();
private TinyImage ball;
private int ballX, ballY;
private int ballVelX, ballVelY;
public TinyGame(){
setLayout(new BorderLayout());
}
public void init(){
int upScale = getWidth()/TinyScreen.WIDTH;
screen = new TinyScreen(upScale);
screen.addFocusListener(this);
state = GameState.TITLE_SCREEN;
add(screen, BorderLayout.CENTER);
screen.addMouseListener(this);
gameThread = new Thread(this);
gameThread.start();
// test game
ball = new TinyImage(7,7,0);
for(int i = 0; i < ball.width; i++){
for(int j = 0; j < ball.height; j++){
int dx = i - 3;
int dy = j - 3;
if (dy*dy + dx*dx <= 9){
ball.setPixel(i, j, 1);
}
}
}
ball.setPixel(2, 2, 15);
ball.setPixel(2, 3, 15);
ball.setPixel(3, 2, 15);
ballVelX = 2;
ballVelY = 1;
ballX = 10;
ballY = 12;
}
public void start(){
if (state == GameState.PAUSED){
state = GameState.RUNNING;
}
}
public void stop(){
if (state == GameState.RUNNING){
state = GameState.PAUSED;
}
}
public void destroy(){
done = true;
try {
gameThread.join();
} catch (InterruptedException e) {}
}
public void focusGained(FocusEvent event) {
start();
}
public void focusLost(FocusEvent event) {
stop();
}
public void stepGame(){
if (ballX < 4 || ballX > 36){
ballVelX = -ballVelX;
}
if (ballY < 4 || ballY > 26){
ballVelY = -ballVelY;
}
ballX += ballVelX;
ballY += ballVelY;
}
public void drawGame(){
screen.fill(0);
screen.setPixel(rand.nextInt(40), rand.nextInt(30), rand.nextInt(16));
screen.drawImage(ball,ballX - 3,ballY - 3);
screen.drawString("test", 12,12,2);
screen.show();
}
public void drawTitle(){
screen.fill(0);
screen.drawString("click to", 4, 8, 3);
screen.drawString(" play", 4, 15, 4);
screen.show();
}
public void drawPause(){
screen.fill(0);
screen.drawString("paused", 8, 12, 3);
screen.show();
}
public void run() {
while(!done){
long delta = System.nanoTime();
synchronized (this){
switch(state){
case RUNNING:
stepGame();
drawGame();
delta = System.nanoTime() - delta;
delta /= 1000000; // to milisecs.
break;
case PAUSED:
delta = 0;
drawPause();
break;
case TITLE_SCREEN:
delta = 0;
drawTitle();
break;
}
}
// try to lock us at 30fps.
int targetDelta = 1000/FRAMES_PER_SEC;
if (delta < targetDelta){
try {
Thread.sleep(targetDelta - (int)delta);
} catch (InterruptedException e) {}
}
}
}
public void mouseClicked(MouseEvent me) {
state = GameState.RUNNING;
screen.requestFocusInWindow();
}
public void mouseEntered(MouseEvent me) {
}
public void mouseExited(MouseEvent me) {
}
public void mousePressed(MouseEvent me) {
}
public void mouseReleased(MouseEvent me) {
}
}
public class TinyScreen extends Canvas {
public static final int[] EGA_COLORS = new int[]{
0xFF000000, 0xFF0000AA, 0xFF00AA00, 0xFF00AAAA, 0xFFAA0000, 0xFFAA00AA, 0xFFAAAA00, 0xFFAAAAAA,
0xFF000055, 0xFF0000FF, 0xFF00AA55, 0xFF00AAFF, 0xFFAA0055, 0xFFAA00FF, 0xFFAAAA55, 0xFFAAAAFF,
0xFF005500, 0xFF0055AA, 0xFF00FF00, 0xFF00FFAA, 0xFFAA5500, 0xFFAA55AA, 0xFFAAFF00, 0xFFAAFFAA,
0xFF005555, 0xFF0055FF, 0xFF00FF55, 0xFF00FFFF, 0xFFAA5555, 0xFFAA55FF, 0xFFAAFF55, 0xFFAAFFFF,
0xFF550000, 0xFF5500AA, 0xFF55AA00, 0xFF55AAAA, 0xFFFF0000, 0xFFFF00AA, 0xFFFFAA00, 0xFFFFAAAA,
0xFF550055, 0xFF5500FF, 0xFF55AA55, 0xFF55AAFF, 0xFFFF0055, 0xFFFF00FF, 0xFFFFAA55, 0xFFFFAAFF,
0xFF555500, 0xFF5555AA, 0xFF55FF00, 0xFF55FFAA, 0xFFFF5500, 0xFFFF55AA, 0xFFFFFF00, 0xFFFFFFAA,
0xFF555555, 0xFF5555FF, 0xFF55FF55, 0xFF55FFFF, 0xFFFF5555, 0xFFFF55FF, 0xFFFFFF55, 0xFFFFFFFF
};
private static final int[] FONT = new int[]{
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x2000,0x17D9,
0x2B6A,0x749A,0x772A,0x38A3,0x49ED,0x39CF,0x2ACE,0x12A7,0x7BEF,0x39AA,
0x0410,0x1410,0x4454,0x0E38,0x1511,0x252A,0x0000,0x5BEA,0x3AEB,0x2A6A,
0x3B6B,0x72CF,0x12CF,0x2B4E,0x5BED,0x3497,0x5AED,0x7249,0x5B7D,0x5FFD,
0x2B6A,0x12EB,0x456A,0x5AEB,0x388E,0x2497,0x2B6D,0x256D,0x2FED,0x5AAD,
0x24AD,0x72A7,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x6B50,
0x3B59,0x6270,0x6B74,0x6750,0x25D6,0x3D50,0x5B59,0x2482,0x1482,0x5749,
0x2492,0x5BF8,0x5B50,0x2B50,0x1758,0x4D70,0x12C8,0x1450,0x24BA,0x2B68,
0x2568,0x7F68,0x54A8,0x3D68,0x77B8,0x0000,0x0000,0x0000,0x0000,0x0000,
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
};
public static final int WIDTH = 40;
public static final int HEIGHT = 30;
private final BufferedImage screenBuffer;
private final int upScale;
public final int[] palette;
public final TinyImage buffer;
public TinyScreen(int upScale){
screenBuffer = new BufferedImage(WIDTH*upScale,HEIGHT*upScale,BufferedImage.TYPE_INT_RGB);
buffer = new TinyImage(WIDTH,HEIGHT);
palette = new int[]{0,1,2,3,4,5,20,7,56,57,58,59,60,61,62,63};
this.upScale = upScale;
setFocusable(true);
setIgnoreRepaint(true);
}
public void setPixel(int x, int y, int value){
buffer.setPixel(x,y,value);
}
private void updateScreenBuffer(){
int[] pixels = ((DataBufferInt)screenBuffer.getRaster().getDataBuffer()).getData();
for(int x = 0; x < WIDTH; x++){
for(int y = 0; y < HEIGHT; y++){
int rgb = EGA_COLORS[palette[buffer.pixels[y*WIDTH + x]]];
for(int i = 0; i < upScale*upScale; i++){
pixels[(y*upScale + i%upScale)*upScale*WIDTH + (x*upScale + i/upScale)] = rgb;
}
}
}
}
public void show(){
BufferStrategy bs = getBufferStrategy();
if (bs == null){
createBufferStrategy(2);
bs = getBufferStrategy();
}
Graphics g = bs.getDrawGraphics();
updateScreenBuffer();
g.drawImage(screenBuffer,0,0,null);
g.dispose();
bs.show();
}
public void fill(int value) {
Arrays.fill(buffer.pixels, value);
}
public void drawString(String s, int x, int y, int color){
for(int i = 0; i < s.length(); i++){
int value = FONT[s.charAt(i) - 28];
for(int j = 0; j < 3*5; j++){
int px = x + j%3 + i*4;
int py = y + j/3;
if (((1 << j) & value) != 0 && px < WIDTH && py < HEIGHT && px >= 0 && py >= 0){
setPixel(px, py, color);
}
}
}
}
public void drawImage(TinyImage src, int dx, int dy){
drawImage(src,dx,dy,src.width,src.height,0,0);
}
public void drawImage(TinyImage src, int dx, int dy, int w, int h, int sx, int sy){
int x1 = Math.max(0,dx);
int x2 = Math.min(buffer.width-1, dx + w);
int y1 = Math.max(0,dy);
int y2 = Math.min(buffer.height-1, dy + h);
w = x2 - x1;
h = y2 - y1;
int transparent = src.getTransparentIndex();
if (w > 0 && h > 0){
for(int row = y1; row < y2; row++){
for(int col = x1; col < x2; col++){
int val = src.pixels[((row - dy) + sy)*src.width + ((col - dx) + sx)];
if (val != transparent){
buffer.pixels[row*buffer.width + col] = val;
}
}
}
}
}
}
public class TinyImage {
public final int width;
public final int height;
public final int[] pixels;
private int transparentIndex;
public TinyImage(int width, int height, int transparentIndex){
this.width = width;
this.height = height;
this.pixels = new int[width*height];
}
public TinyImage(int width, int height){
this(width,height,-1);
}
public void setPixel(int x, int y, int value){
pixels[y*width + x] = value;
}
public int getTransparentIndex(){
return transparentIndex;
}
}