1
+ package com .zetcode ;
2
+
3
+ import java .awt .Color ;
4
+ import java .awt .Dimension ;
5
+ import java .awt .Graphics ;
6
+ import java .awt .event .KeyAdapter ;
7
+ import java .awt .event .KeyEvent ;
8
+ import javax .swing .JLabel ;
9
+ import javax .swing .JPanel ;
10
+ import java .util .Timer ;
11
+
12
+ import java .util .TimerTask ;
13
+ import com .zetcode .Shape .Tetrominoe ;
14
+
15
+ public class Board extends JPanel {
16
+
17
+ private static final long serialVersionUID = 1L ;
18
+ private final int BOARD_WIDTH = 10 ;
19
+ private final int BOARD_HEIGHT = 22 ;
20
+ private final int INITIAL_DELAY = 100 ;
21
+ private final int PERIOD_INTERVAL = 300 ;
22
+
23
+ private Timer timer ;
24
+ private boolean isFallingFinished = false ;
25
+ private boolean isStarted = false ;
26
+ private boolean isPaused = false ;
27
+ private int numLinesRemoved = 0 ;
28
+ private int curX = 0 ;
29
+ private int curY = 0 ;
30
+ private JLabel statusbar ;
31
+ private Shape curPiece ;
32
+ private Tetrominoe [] board ;
33
+
34
+ public Board (Tetris parent ) {
35
+
36
+ initBoard (parent );
37
+ }
38
+
39
+ private void initBoard (Tetris parent ) {
40
+
41
+ setFocusable (true );
42
+ timer = new Timer ();
43
+ timer .scheduleAtFixedRate (new ScheduleTask (),
44
+ INITIAL_DELAY , PERIOD_INTERVAL );
45
+
46
+ curPiece = new Shape ();
47
+
48
+ statusbar = parent .getStatusBar ();
49
+ board = new Tetrominoe [BOARD_WIDTH * BOARD_HEIGHT ];
50
+ addKeyListener (new TAdapter ());
51
+ clearBoard ();
52
+ }
53
+
54
+ private int squareWidth () {
55
+ return (int ) getSize ().getWidth () / BOARD_WIDTH ;
56
+ }
57
+
58
+ private int squareHeight () {
59
+ return (int ) getSize ().getHeight () / BOARD_HEIGHT ;
60
+ }
61
+
62
+ private Tetrominoe shapeAt (int x , int y ) {
63
+ return board [(y * BOARD_WIDTH ) + x ];
64
+ }
65
+
66
+ public void start () {
67
+
68
+ isStarted = true ;
69
+ clearBoard ();
70
+ newPiece ();
71
+ }
72
+
73
+ private void pause () {
74
+
75
+ if (!isStarted ) {
76
+ return ;
77
+ }
78
+
79
+ isPaused = !isPaused ;
80
+
81
+ if (isPaused ) {
82
+
83
+ statusbar .setText ("Paused" );
84
+ } else {
85
+
86
+ statusbar .setText (String .valueOf (numLinesRemoved ));
87
+ }
88
+ }
89
+
90
+ private void doDrawing (Graphics g ) {
91
+
92
+ Dimension size = getSize ();
93
+ int boardTop = (int ) size .getHeight () - BOARD_HEIGHT * squareHeight ();
94
+
95
+ for (int i = 0 ; i < BOARD_HEIGHT ; ++i ) {
96
+
97
+ for (int j = 0 ; j < BOARD_WIDTH ; ++j ) {
98
+
99
+ Tetrominoe shape = shapeAt (j , BOARD_HEIGHT - i - 1 );
100
+
101
+ if (shape != Tetrominoe .NoShape ) {
102
+
103
+ drawSquare (g , 0 + j * squareWidth (),
104
+ boardTop + i * squareHeight (), shape );
105
+ }
106
+ }
107
+ }
108
+
109
+ if (curPiece .getShape () != Tetrominoe .NoShape ) {
110
+
111
+ for (int i = 0 ; i < 4 ; ++i ) {
112
+
113
+ int x = curX + curPiece .x (i );
114
+ int y = curY - curPiece .y (i );
115
+ drawSquare (g , 0 + x * squareWidth (),
116
+ boardTop + (BOARD_HEIGHT - y - 1 ) * squareHeight (),
117
+ curPiece .getShape ());
118
+ }
119
+ }
120
+ }
121
+
122
+ @ Override
123
+ public void paintComponent (Graphics g ) {
124
+
125
+ super .paintComponent (g );
126
+ doDrawing (g );
127
+ }
128
+
129
+ private void dropDown () {
130
+
131
+ int newY = curY ;
132
+
133
+ while (newY > 0 ) {
134
+
135
+ if (!tryMove (curPiece , curX , newY - 1 )) {
136
+
137
+ break ;
138
+ }
139
+
140
+ --newY ;
141
+ }
142
+
143
+ pieceDropped ();
144
+ }
145
+
146
+ private void oneLineDown () {
147
+
148
+ if (!tryMove (curPiece , curX , curY - 1 )) {
149
+
150
+ pieceDropped ();
151
+ }
152
+ }
153
+
154
+ private void clearBoard () {
155
+
156
+ for (int i = 0 ; i < BOARD_HEIGHT * BOARD_WIDTH ; ++i ) {
157
+ board [i ] = Tetrominoe .NoShape ;
158
+ }
159
+ }
160
+
161
+ private void pieceDropped () {
162
+
163
+ for (int i = 0 ; i < 4 ; ++i ) {
164
+
165
+ int x = curX + curPiece .x (i );
166
+ int y = curY - curPiece .y (i );
167
+ board [(y * BOARD_WIDTH ) + x ] = curPiece .getShape ();
168
+ }
169
+
170
+ removeFullLines ();
171
+
172
+ if (!isFallingFinished ) {
173
+ newPiece ();
174
+ }
175
+ }
176
+
177
+ private void newPiece () {
178
+
179
+ curPiece .setRandomShape ();
180
+ curX = BOARD_WIDTH / 2 + 1 ;
181
+ curY = BOARD_HEIGHT - 1 + curPiece .minY ();
182
+
183
+ if (!tryMove (curPiece , curX , curY )) {
184
+
185
+ curPiece .setShape (Tetrominoe .NoShape );
186
+ timer .cancel ();
187
+ isStarted = false ;
188
+ statusbar .setText ("GAME OVER!" );
189
+ }
190
+ }
191
+
192
+ private boolean tryMove (Shape newPiece , int newX , int newY ) {
193
+
194
+ for (int i = 0 ; i < 4 ; ++i ) {
195
+
196
+ int x = newX + newPiece .x (i );
197
+ int y = newY - newPiece .y (i );
198
+
199
+ if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT ) {
200
+ return false ;
201
+ }
202
+
203
+ if (shapeAt (x , y ) != Tetrominoe .NoShape ) {
204
+ return false ;
205
+ }
206
+ }
207
+
208
+ curPiece = newPiece ;
209
+ curX = newX ;
210
+ curY = newY ;
211
+
212
+ repaint ();
213
+
214
+ return true ;
215
+ }
216
+
217
+ private void removeFullLines () {
218
+
219
+ int numFullLines = 0 ;
220
+
221
+ for (int i = BOARD_HEIGHT - 1 ; i >= 0 ; --i ) {
222
+ boolean lineIsFull = true ;
223
+
224
+ for (int j = 0 ; j < BOARD_WIDTH ; ++j ) {
225
+
226
+ if (shapeAt (j , i ) == Tetrominoe .NoShape ) {
227
+
228
+ lineIsFull = false ;
229
+ break ;
230
+ }
231
+ }
232
+
233
+ if (lineIsFull ) {
234
+
235
+ ++numFullLines ;
236
+
237
+ for (int k = i ; k < BOARD_HEIGHT - 1 ; ++k ) {
238
+ for (int j = 0 ; j < BOARD_WIDTH ; ++j ) {
239
+
240
+ board [(k * BOARD_WIDTH ) + j ] = shapeAt (j , k + 1 );
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ if (numFullLines > 0 ) {
247
+
248
+ numLinesRemoved += numFullLines ;
249
+ statusbar .setText (String .valueOf (numLinesRemoved ));
250
+ isFallingFinished = true ;
251
+ curPiece .setShape (Tetrominoe .NoShape );
252
+ repaint ();
253
+ }
254
+ }
255
+
256
+ private void drawSquare (Graphics g , int x , int y ,
257
+ Tetrominoe shape ) {
258
+
259
+ Color colors [] = {
260
+ new Color (0 , 0 , 0 ), new Color (204 , 102 , 102 ),
261
+ new Color (102 , 204 , 102 ), new Color (102 , 102 , 204 ),
262
+ new Color (204 , 204 , 102 ), new Color (204 , 102 , 204 ),
263
+ new Color (102 , 204 , 204 ), new Color (218 , 170 , 0 )
264
+ };
265
+
266
+ Color color = colors [shape .ordinal ()];
267
+
268
+ g .setColor (color );
269
+ g .fillRect (x + 1 , y + 1 , squareWidth () - 2 , squareHeight () - 2 );
270
+
271
+ g .setColor (color .brighter ());
272
+ g .drawLine (x , y + squareHeight () - 1 , x , y );
273
+ g .drawLine (x , y , x + squareWidth () - 1 , y );
274
+
275
+ g .setColor (color .darker ());
276
+ g .drawLine (x + 1 , y + squareHeight () - 1 ,
277
+ x + squareWidth () - 1 , y + squareHeight () - 1 );
278
+ g .drawLine (x + squareWidth () - 1 , y + squareHeight () - 1 ,
279
+ x + squareWidth () - 1 , y + 1 );
280
+
281
+ }
282
+
283
+ private void doGameCycle () {
284
+
285
+ update ();
286
+ repaint ();
287
+ }
288
+
289
+ private void update () {
290
+
291
+ if (isPaused ) {
292
+ return ;
293
+ }
294
+
295
+ if (isFallingFinished ) {
296
+
297
+ isFallingFinished = false ;
298
+ newPiece ();
299
+ } else {
300
+
301
+ oneLineDown ();
302
+ }
303
+ }
304
+
305
+ private class TAdapter extends KeyAdapter {
306
+
307
+ @ Override
308
+ public void keyPressed (KeyEvent e ) {
309
+
310
+ System .out .println ("key pressed" );
311
+
312
+ if (!isStarted || curPiece .getShape () == Tetrominoe .NoShape ) {
313
+ return ;
314
+ }
315
+
316
+ int keycode = e .getKeyCode ();
317
+
318
+ if (keycode == KeyEvent .VK_P ) {
319
+ pause ();
320
+ return ;
321
+ }
322
+
323
+ if (isPaused ) {
324
+ return ;
325
+ }
326
+
327
+ switch (keycode ) {
328
+
329
+ case KeyEvent .VK_LEFT :
330
+ tryMove (curPiece , curX - 1 , curY );
331
+ break ;
332
+
333
+ case KeyEvent .VK_RIGHT :
334
+ tryMove (curPiece , curX + 1 , curY );
335
+ break ;
336
+
337
+ case KeyEvent .VK_DOWN :
338
+ tryMove (curPiece .rotateRight (), curX , curY );
339
+ break ;
340
+
341
+ case KeyEvent .VK_UP :
342
+ tryMove (curPiece .rotateLeft (), curX , curY );
343
+ break ;
344
+
345
+ case KeyEvent .VK_SPACE :
346
+ dropDown ();
347
+ break ;
348
+
349
+ case KeyEvent .VK_D :
350
+ oneLineDown ();
351
+ break ;
352
+ }
353
+ }
354
+ }
355
+
356
+ private class ScheduleTask extends TimerTask {
357
+
358
+ @ Override
359
+ public void run () {
360
+
361
+ doGameCycle ();
362
+ }
363
+ }
364
+ }
0 commit comments