本文实例为大家分享了java实现2048小游戏的具体代码,供大家参考,具体内容如下
一、实现效果
二、实现代码
Check表示格子,GameView实现游戏视图界面及功能,是核心。
Check.java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import java.awt.Color; import java.awt.Font;
// 方格类 public class Check { public int value;
Font font1 = new Font( "宋体" , Font.BOLD, 46 ); Font font2 = new Font( "宋体" , Font.BOLD, 40 ); Font font3 = new Font( "宋体" , Font.BOLD, 34 ); Font font4 = new Font( "宋体" , Font.BOLD, 28 ); Font font5 = new Font( "宋体" , Font.BOLD, 22 );
public Check() { value = 0 ; //value为方格中数字 } //字体颜色 public Color getForeground() { switch (value) { case 0 : return new Color( 0xcdc1b4 ); //0的颜色与背景色一致,相当于没有数字 case 2 : case 4 : return Color.BLACK; default : return Color.WHITE; } }
//字体背景颜色,即方格颜色 public Color getBackground() { switch (value) { case 0 : return new Color( 0xcdc1b4 ); case 2 : return new Color( 0xeee4da ); case 4 : return new Color( 0xede0c8 ); case 8 : return new Color( 0xf2b179 ); case 16 : return new Color( 0xf59563 ); case 32 : return new Color( 0xf67c5f ); case 64 : return new Color( 0xf65e3b ); case 128 : return new Color( 0xedcf72 ); case 256 : return new Color( 0xedcc61 ); case 512 : return new Color( 0xedc850 ); case 1024 : return new Color( 0xedc53f ); case 2048 : return new Color( 0xedc22e ); case 4096 : return new Color( 0x65da92 ); case 8192 : return new Color( 0x5abc65 ); case 16384 : return new Color( 0x248c51 ); default : return new Color( 0x248c51 ); } }
public Font getCheckFont() { if (value < 10 ) { return font1; } if (value < 100 ) { return font2; } if (value < 1000 ) { return font3; } if (value < 10000 ) { return font4; }
return font5; }
} |
GameView.java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.Random;
import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel;
public class GameView{ private static final int jframeWidth = 405 ; //窗口宽高 private static final int jframeHeight = 530 ; private static int score = 0 ;
Font topicFont = new Font( "微软雅黑" , Font.BOLD, 50 ); //主题字体 Font scoreFont = new Font( "微软雅黑" , Font.BOLD, 28 ); //得分字体 Font explainFont = new Font( "宋体" , Font.PLAIN, 20 ); //提示字体
private JFrame jframeMain; private JLabel jlblTitle; private JLabel jlblScoreName; private JLabel jlblScore; private JLabel jlblTip; private GameBoard gameBoard;
public GameView() { init(); }
public void init() { //1、创建窗口 jframeMain = new JFrame( "2048小游戏" ); jframeMain.setSize(jframeWidth, jframeHeight); jframeMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jframeMain.setLocationRelativeTo( null ); //窗口显示位置居中 jframeMain.setResizable( false ); jframeMain.setLayout( null ); //设置绝对布局,以便后面可以用setBounds设置位置
jlblTitle = new JLabel( "2048" , JLabel.CENTER); jlblTitle.setFont(topicFont); jlblTitle.setForeground(Color.BLACK); jlblTitle.setBounds( 50 , 0 , 150 , 60 ); jframeMain.add(jlblTitle);
//2、框架窗口搭建好,则需向里面开始添加内容 //设置字体及其颜色、位置 jlblScoreName = new JLabel( "得 分" , JLabel.CENTER); jlblScoreName.setFont(scoreFont); jlblScoreName.setForeground(Color.WHITE); jlblScoreName.setOpaque( true ); jlblScoreName.setBackground(Color.GRAY); jlblScoreName.setBounds( 250 , 0 , 120 , 30 ); jframeMain.add(jlblScoreName);
//3、得分区(得分名+分数) jlblScore = new JLabel( "0" , JLabel.CENTER); jlblScore.setFont(scoreFont); jlblScore.setForeground(Color.WHITE); jlblScore.setOpaque( true ); jlblScore.setBackground(Color.GRAY); jlblScore.setBounds( 250 , 30 , 120 , 30 ); jframeMain.add(jlblScore);
//4、提示说明区 jlblTip = new JLabel( "操作: ↑ ↓ ← →, 按esc键重新开始 " , JLabel.CENTER); jlblTip.setFont(explainFont); jlblTip.setForeground(Color.DARK_GRAY); jlblTip.setBounds( 0 , 60 , 400 , 40 ); jframeMain.add(jlblTip);
//5、主游戏面板区 gameBoard = new GameBoard(); gameBoard.setBounds( 0 , 100 , 400 , 400 ); gameBoard.setBackground(Color.GRAY); gameBoard.setFocusable( true ); //焦点即当前正在操作的组件,也就是移动的数字 gameBoard.setLayout( new FlowLayout()); jframeMain.add(gameBoard); }
// 游戏面板 class GameBoard extends JPanel implements KeyListener { private static final int CHECK_GAP = 10 ; //方格之间的间隙 private static final int CHECK_SIZE = 85 ; //方格大小 private static final int CHECK_ARC = 20 ; //方格弧度
private Check[][] checks = new Check[ 4 ][ 4 ]; private boolean isadd = true ;
public GameBoard() { initGame(); addKeyListener( this ); }
private void initGame() { score = 0 ; for ( int indexRow = 0 ; indexRow < 4 ; indexRow++) { for ( int indexCol = 0 ; indexCol < 4 ; indexCol++) { checks[indexRow][indexCol] = new Check(); } } // 最开始时生成两个数 isadd = true ; createCheck(); isadd = true ; createCheck(); }
@Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_ESCAPE: initGame(); //重新开始游戏(初始化游戏) break ; case KeyEvent.VK_LEFT: moveLeft(); createCheck(); //调用一次方法创建一个方格数字 judgeGameOver(); //创建后判断是否GameOver,若所有格子均满即跳出GameOver break ; case KeyEvent.VK_RIGHT: moveRight(); createCheck(); judgeGameOver(); break ; case KeyEvent.VK_UP: moveUp(); createCheck(); judgeGameOver(); break ; case KeyEvent.VK_DOWN: moveDown(); createCheck(); judgeGameOver(); break ; default : break ; //按其他键没有反应 } repaint(); //刷新,会自动调用paint()方法,重新绘制移动后的图 }
private void createCheck() { List<Check> list = getEmptyChecks();
if (!list.isEmpty() && isadd) { Random random = new Random(); int index = random.nextInt(list.size()); Check check = list.get(index); // 2, 4出现概率3:1 int randomValue = random.nextInt( 4 ); check.value = ( randomValue % 3 == 0 || randomValue % 3 == 1 ) ? 2 : 4 ; //只有[0,4)中的2才能生成4 isadd = false ; } }
// 获取空白方格 private List<Check> getEmptyChecks() { List<Check> checkList = new ArrayList<>(); for ( int i = 0 ; i < 4 ; i++) { for ( int j = 0 ; j < 4 ; j++) { if (checks[i][j].value == 0 ) { checkList.add(checks[i][j]); } } } return checkList; } //是否全部格子占满,全部占满则GameOver private boolean judgeGameOver() { jlblScore.setText(score + "" );
if (!getEmptyChecks().isEmpty()) { return false ; }
for ( int i = 0 ; i < 3 ; i++) { for ( int j = 0 ; j < 3 ; j++) { //判断是否存在可合并的方格 if (checks[i][j].value == checks[i][j + 1 ].value || checks[i][j].value == checks[i + 1 ][j].value) { return false ; } } }
return true ; }
private void moveLeft() { //找到一个非空格子后checks[i][j].value > 0,可分为三种情况处理 for ( int i = 0 ; i < 4 ; i++) { for ( int j = 1 , index = 0 ; j < 4 ; j++) { if (checks[i][j].value > 0 ) { //第一种情况:checks[i][j](非第1列)与checks[i][index]的数相等,则合并乘以2,且得分增加 if (checks[i][j].value == checks[i][index].value) { score += checks[i][index].value *= 2 ; checks[i][j].value = 0 ; isadd = true ; } else if (checks[i][index].value == 0 ) { //第二种:若checks[i][index]为空格子,checks[i][j]就直接移到最左边checks[i][index] checks[i][index].value = checks[i][j].value; checks[i][j].value = 0 ; isadd = true ; } else if (checks[i][++index].value == 0 ) { //第三种:若checks[i][index]不为空格子,并且数字也不相等,若其旁边为空格子,则移到其旁边 checks[i][index].value = checks[i][j].value; checks[i][j].value = 0 ; isadd = true ; } } } } }
private void moveRight() { for ( int i = 0 ; i < 4 ; i++) { for ( int j = 2 , index = 3 ; j >= 0 ; j--) { if (checks[i][j].value > 0 ) { if (checks[i][j].value == checks[i][index].value) { score += checks[i][index].value *= 2 ; checks[i][j].value = 0 ; isadd = true ; } else if (checks[i][index].value == 0 ) { checks[i][index].value = checks[i][j].value; checks[i][j].value = 0 ; isadd = true ; } else if (checks[i][--index].value == 0 ) { checks[i][index].value = checks[i][j].value; checks[i][j].value = 0 ; isadd = true ; } } } } }
private void moveUp() { for ( int i = 0 ; i < 4 ; i++) { for ( int j = 1 , index = 0 ; j < 4 ; j++) { if (checks[j][i].value > 0 ) { if (checks[j][i].value == checks[index][i].value) { score += checks[index][i].value *= 2 ; checks[j][i].value = 0 ; isadd = true ; } else if (checks[index][i].value == 0 ) { checks[index][i].value = checks[j][i].value; checks[j][i].value = 0 ; isadd = true ; } else if (checks[++index][i].value == 0 ){ checks[index][i].value = checks[j][i].value; checks[j][i].value = 0 ; isadd = true ; } } } } }
private void moveDown() { for ( int i = 0 ; i < 4 ; i++) { for ( int j = 2 , index = 3 ; j >= 0 ; j--) { if (checks[j][i].value > 0 ) { if (checks[j][i].value == checks[index][i].value) { score += checks[index][i].value *= 2 ; checks[j][i].value = 0 ; isadd = true ; } else if (checks[index][i].value == 0 ) { checks[index][i].value = checks[j][i].value; checks[j][i].value = 0 ; isadd = true ; } else if (checks[--index][i].value == 0 ) { checks[index][i].value = checks[j][i].value; checks[j][i].value = 0 ; isadd = true ; } } } } }
@Override public void paint(Graphics g) { super .paint(g); for ( int i = 0 ; i < 4 ; i++) { for ( int j = 0 ; j < 4 ; j++) { drawCheck(g, i, j); } }
// GameOver if (judgeGameOver()) { g.setColor( new Color( 64 , 64 , 64 , 100 )); //RGBA最后一个A可以视为透明度 g.fillRect( 0 , 0 , getWidth(), getHeight()); //填充矩形(游戏面板),将暗黑色填充上去 g.setColor(Color.WHITE); g.setFont(topicFont); FontMetrics fms = getFontMetrics(topicFont); //FontMetrics字体测量,该类是Paint的内部类,通过getFontMetrics()方法可获取字体相关属性 String value = "Game Over!" ; g.drawString(value, (getWidth()-fms.stringWidth(value)) / 2 , getHeight() / 2 ); //字体居中显示 } }
// 绘制方格 // Graphics2D 类是Graphics 子类,拥有强大的二维图形处理能力 private void drawCheck(Graphics g, int i, int j) { Graphics2D gg = (Graphics2D) g; //下面两句是抗锯齿模式,计算和优化消除文字锯齿,字体更清晰顺滑 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); //获取方格 Check check = checks[i][j]; //不同数字设置背景色 gg.setColor(check.getBackground()); // 绘制圆角 gg.fillRoundRect(CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j, CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i, CHECK_SIZE, CHECK_SIZE, CHECK_ARC, CHECK_ARC); //绘制字体及其颜色 gg.setColor(check.getForeground()); gg.setFont(check.getCheckFont());
// 文字测量,并对文字进行绘制 FontMetrics fms = getFontMetrics(check.getCheckFont()); String value = String.valueOf(check.value); //使用此图形上下文的当前颜色绘制由指定迭代器给定的文本。 //getAscent()是FontMetrics中的一个方法, //getDescent() 为降部 gg.drawString(value, CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j + (CHECK_SIZE - fms.stringWidth(value)) / 2 , CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i + (CHECK_SIZE - fms.getAscent() - fms.getDescent()) / 2 + fms.getAscent()); //让数字居中显示 }
@Override public void keyReleased(KeyEvent e) { }
@Override public void keyTyped(KeyEvent e) { }
}
public void showView() { jframeMain.setVisible( true ); }
} |
Main.java
|
1 2 3 4 5 |
public class Main { public static void main(String[] args) { new GameView().showView(); } } |
三、重难点讲解
3.1 数字移动问题
数字移动是一难点,分三种情况,以moveLeft()为例
(1)按左键,若最左边是相同的,则合并
(2)若左边是空格,则直接移动到最左即可
(3)若最左边不为空格,且不相等,则看它右边是否是空格,是则移动到其旁边
3.2 绘图问题—抗锯齿
java提供的Graphics 2D,它是Graphics 子类
|
1 2 3 |
Graphics2D gg = (Graphics2D) g; gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE); |
上面这两个语句实现的功能是消除文字锯齿,字体更清晰顺滑,可以看下图没有setRenderingHint和有setRenderingHint的区别
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
原文链接:https://blog.csdn.net/weixin_39615182/article/details/113502243