I made a 2048 game clone in Java for training, and I am trying to make it more efficient.
The problem I found is the paintComponent()
method always resets all the graphics before doing anything, so I am forced to repaint all 4 rows of the game every time I want to repaint. Obviously I didn't want to override paint()
and/or the update()
method because I don't know how to correctly override them and it's not a good idea anyway.
The game works so far, but, before I proceed further, I am looking for ideas on how to only repaint the rows that get affected. For example, if the player presses left and only the bottom two rows get affected and the top ones don't change at all, I only want to redraw the bottom rows for efficiency. I had this idea of having an array of 4 int
elements where each element represents a row. If the is_moved
flag becomes true
I set that row in that array to equal 0. If that row didn't change, the index in the array for that row will be -1. But still, because of paintComponent()
resetting everything beforehand, I have to every time I want to repaint()
. I have to redraw all rows, even if some of them don't need to be repainted.
(removed all instances of the byte array temp, I forgot to delete it before posting, now it's gone)
public class Game_Panel extends JPanel implements KeyListener
{
//instance variables
public TILE panel[][];
public byte current_tiles;
public boolean achieved_goal;
public static final int default_start_A = 2;
public static final int default_start_B = 4;
public static final int HW = 489;
public static final int seperation_length = 4;
public static final int block_width = 119;
public static final int block_center = 119>>1;
public static final int RANDOM = 101;
public static final byte ROWS_COLS = 4;
public static final byte real_end = ROWS_COLS-1;
public static final byte fake_end = 0;
public static final byte left_increment = 1;
public static final byte right_increment = -1;
//keyboard ascii numbers
public static final byte LEFT = 37;
public static final byte RIGHT = 39;
public static final byte UP = 38;
public static final byte DOWN = 40;
//Colors of different numbers
public static final Color BACKGROUND = new Color(187,173,160);
public static final Color DEFAULT_TILE = new Color(204,192,179);
public static final Color TWO = new Color(238,228,218);
public static final Color FOUR = new Color(237,224,200);
public static final Color EIGHT = new Color(242,177,121);
public static final Color SIXTEEN = new Color(245,149,98);
public static final Color THIRTYTWO = new Color(246,124,95);
public static final Color SIXTYFOUR = new Color(246,94,59);
public static final Color REMAINING = new Color(237,204,97);
//x and y positions of the four possible places of a tile.
public static final int JUMPS[] = {seperation_length,
(block_width+seperation_length),
((block_width<<1)+seperation_length),
(((block_width<<1)+block_width)+seperation_length)};
public boolean is_moved = false;
public final Font END = new Font("Lithograph", Font.BOLD, 50);
public static final RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
public Game_Panel()
{
setBackground(BACKGROUND);
setPreferredSize(new Dimension(HW,HW));
setFocusable(true);
requestFocusInWindow();
addKeyListener(this);
rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
panel = new TILE[ROWS_COLS][ROWS_COLS];
achieved_goal = false;
//same as generate method, but thought it'd be a waste
//to call it when we're initializing.
Random row_col = new Random();
byte row = (byte) row_col.nextInt(ROWS_COLS);
byte col = (byte) row_col.nextInt(ROWS_COLS);
int two_four = row_col.nextInt(RANDOM);
if (two_four % 2 == 0)
{
panel[row][col] = new TILE(default_start_A);
}
else
{
panel[row][col] = new TILE(default_start_B);
}
current_tiles++;
}
public void paintComponent(Graphics g_first)
{
super.paintComponent(g_first);
Graphics2D g = (Graphics2D) g_first;
g.setRenderingHints(rh);
for (byte row=0; row<ROWS_COLS; row++)
{
int Y_jump = JUMPS[row];
for (byte col=0; col<ROWS_COLS; col++)
{
int X_jump = JUMPS[col];
if (panel[row][col] == null)
{
g.setColor(DEFAULT_TILE);
g.fillRoundRect(X_jump, Y_jump, block_width, block_width, 80, 80);
}
else
{
int value = panel[row][col].value;
JLabel temp = panel[row][col].LABEL;
if (value == 2)
{
g.setColor(TWO);
temp.setLocation(X_jump+block_center-18, Y_jump+block_center-20);
}
else if (value == 4)
{
g.setColor(FOUR);
temp.setLocation(X_jump+block_center-18, Y_jump+block_center-20);
}
else if (value == 8)
{
g.setColor(EIGHT);
temp.setLocation(X_jump+block_center-18, Y_jump+block_center-20);
}
else if (value == 16)
{
g.setColor(SIXTEEN);
temp.setLocation(X_jump+block_center-28, Y_jump+block_center-23);
}
else if (value == 32)
{
g.setColor(THIRTYTWO);
temp.setLocation(X_jump+block_center-28, Y_jump+block_center-23);
}
else if (value == 64)
{
g.setColor(SIXTYFOUR);
temp.setLocation(X_jump+block_center-30, Y_jump+block_center-23);
}
else if (value < 1024)
{
g.setColor(REMAINING);
temp.setLocation(X_jump+block_center-45, Y_jump+block_center-20);
}
else
{
g.setColor(REMAINING);
temp.setFont(panel[row][col].big_number);
temp.setLocation(X_jump+block_center-45, Y_jump+block_center-15);
}
g.fillRoundRect(X_jump, Y_jump, block_width, block_width, 80, 80);
add(temp);
}
}
}
if (!achieved_goal)
{
if (current_tiles == 16)
{
boolean check = false;
for (byte x=0; x<ROWS_COLS; x++)
{
try{
byte y=0;
while (y!=ROWS_COLS)
{
if (y+1 <= real_end && x+1 <= real_end)
{
if (panel[x][y].value == panel[x][y+1].value || panel[x][y].value == panel[x+1][y].value)
{
check = true;
break;
}
else
{
y++;
}
}
else if (y+1 <= real_end)
{
if (panel[x][y].value == panel[x][y+1].value)
{
check = true;
break;
}
else
{
y++;
}
}
else
{
if (panel[x][y].value == panel[x+1][y].value)
{
check = true;
break;
}
else
{
y++;
}
}
}
}
catch (ArrayIndexOutOfBoundsException e){
break;
}
if (check)
{
break;
}
}
if (!check)
{
System.out.println("YOU LOSE BAKA!!");
setEnabled(false);
try {
this.finalize();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
else
{
System.out.println("YOU WIN!!");
setEnabled(false);
try {
this.finalize();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
//dummy methods
public void keyReleased(KeyEvent e){}
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e)
{
is_moved=false;
byte key = (byte)e.getKeyCode();
if (key == LEFT)
{
is_moved = horizontal_pressed(real_end, left_increment);
}
else if (key == RIGHT)
{
is_moved = horizontal_pressed(fake_end, right_increment);
}
else if (key == UP)
{
panel = rotateRight(panel);
is_moved = horizontal_pressed(fake_end, right_increment);
panel = rotateLeft(panel);
}
else if (key == DOWN)
{
panel = rotateRight(panel);
is_moved = horizontal_pressed(real_end,left_increment);
panel = rotateLeft(panel);
}
Generate(is_moved);
repaint();
}
public boolean horizontal_pressed(byte left_or_right, byte increment)
{
byte compare = (byte)(increment+left_or_right);
byte which_end = (byte)(real_end-left_or_right);
for (byte row=0; row<ROWS_COLS; row++)
{
shift_row(row,which_end,compare,increment);
}
//merge_row
for (byte y=0; y<ROWS_COLS; y++)
{
byte x = which_end;
while (x != compare && x != left_or_right)
{
if (panel[y][x] != null && panel[y][x+increment] != null && panel[y][x].value == panel[y][x+increment].value)
{
panel[y][x].doubleValue();
remove(panel[y][x+increment].LABEL);
panel[y][x+increment] = null;
current_tiles--;
is_moved = true;
x = (byte)(x+(increment+increment));
}
else
{
x = (byte)(x+increment);
}
}
shift_row(y,which_end,compare,increment);
}
return is_moved;
}
public void shift_row(byte row, byte which_end, byte compare, byte increment)
{
ArrayList<TILE> temp_row = new ArrayList<TILE>();
byte col;
for (col = which_end; col!=compare; col = (byte)(col+increment))
{
if (panel[row][col] != null)
{
temp_row.add(panel[row][col]);
}
}
byte next = 0;
for (col=which_end; col!=compare; col= (byte)(col+increment))
{
try {
if (temp_row.get(next) != panel[row][col])
{
is_moved = true;
panel[row][col] = temp_row.get(next);
}
}
catch (IndexOutOfBoundsException E) {
panel[row][col] = null;
}
next++;
}
}
public void Generate(boolean is_moved)
{
if (is_moved)
{
Random row_col = new Random();
byte row = (byte) row_col.nextInt(ROWS_COLS);
byte col = (byte) row_col.nextInt(ROWS_COLS);
int two_four = row_col.nextInt(RANDOM);
if (two_four % 2 == 0)
{
if (panel[row][col] == null)
{
panel[row][col] = new TILE(default_start_A);
current_tiles++;
}
else
{
Generate(is_moved);
}
}
else
{
if (panel[row][col] == null)
{
panel[row][col] = new TILE(default_start_B);
current_tiles++;
}
else
{
Generate(is_moved);
}
}
}
}
public TILE[][] rotateLeft(TILE image[][])
{
TILE new_image[][] = new TILE[ROWS_COLS][ROWS_COLS];
for (int y=0; y<ROWS_COLS; y++)
{
for (int x=0; x<ROWS_COLS; x++)
{
new_image[x][y] = image[y][real_end - x];
}
}
return new_image;
}
public TILE[][] rotateRight(TILE image[][])
{
TILE new_image[][] = new TILE[ROWS_COLS][ROWS_COLS];
for (int y=0; y<ROWS_COLS; y++)
{
for (int x=0; x<ROWS_COLS; x++)
{
new_image[x][real_end - y] = image[y][x];
}
}
return new_image;
}
class TILE
{
public int value;
public JLabel LABEL;
public final Font font = new Font("Lithograph", Font.BOLD, 50);
public final Font big_number = new Font("Lithograph", Font.BOLD, 35);
public final Color DEFAULT = new Color(119,110,101);
public final Color REMAINING = new Color(249,246,242);
public TILE(int value)
{
this.value = value;
LABEL = new JLabel(Integer.toString(value));
LABEL.setSize(40,40);
LABEL.setFont(font);
LABEL.setForeground(DEFAULT);
}
public void doubleValue()
{
value = value<<1;
LABEL.setText(Integer.toString(value));
if (value > 4)
{
LABEL.setForeground(REMAINING);
}
if (value == 2048)
{
Game_Panel.this.achieved_goal = true;
}
}
}
public static void main(String argc[])
{
System.setProperty("awt.useSystemAAFontSettings","on");
System.setProperty("swing.aatext", "true");
JFrame window = new JFrame();
window.setSize(new Dimension(HW,HW));
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.setContentPane(new Game_Panel());
window.pack();
window.setVisible(true);
}
}