0
|
1 /*
|
9
|
2 * Ristipolku Game Engine
|
0
|
3 * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
|
|
4 */
|
9
|
5 package game;
|
|
6
|
0
|
7 import java.awt.*;
|
|
8 import java.awt.geom.*;
|
1
|
9 import java.awt.event.*;
|
7
|
10 import java.awt.image.*;
|
|
11 import java.awt.event.*;
|
9
|
12 import javax.imageio.*;
|
7
|
13 import javax.swing.*;
|
1
|
14 import java.util.*;
|
6
|
15 import java.io.*;
|
1
|
16 import game.*;
|
|
17
|
8
|
18 import javax.sound.sampled.*;
|
|
19
|
2
|
20
|
21
|
21 enum Sound
|
|
22 {
|
|
23 PIECE_PLACED("placed.wav", true),
|
|
24
|
|
25 MUSIC_GAME1("gamemusic.wav", false);
|
18
|
26
|
|
27
|
21
|
28 private final String name;
|
|
29 private final boolean effect;
|
|
30
|
|
31 Sound(String name, boolean effect)
|
18
|
32 {
|
|
33 this.name = name;
|
21
|
34 this.effect = effect;
|
18
|
35 }
|
21
|
36
|
|
37 public String getName()
|
18
|
38 {
|
21
|
39 return this.name;
|
18
|
40 }
|
|
41
|
21
|
42 public boolean isEffect()
|
|
43 {
|
|
44 return effect;
|
|
45 }
|
|
46 }
|
|
47
|
|
48
|
|
49 class SoundElement implements Runnable
|
|
50 {
|
|
51 private final String name;
|
|
52 private Clip clip;
|
|
53 private AudioInputStream stream;
|
|
54 private AudioFormat format;
|
|
55 private SourceDataLine line;
|
|
56 private Thread playThread;
|
|
57 private int loopCount;
|
|
58 private boolean doPlay;
|
|
59
|
|
60 SoundElement(String filename, boolean effect) throws IOException
|
18
|
61 {
|
21
|
62 this.name = filename;
|
|
63
|
|
64 ResourceLoader res = new ResourceLoader(name);
|
|
65 if (res == null || res.getStream() == null)
|
|
66 {
|
|
67 throw new IOException("Could not load audio resource '"+name+"'.\n");
|
|
68 }
|
|
69
|
|
70 try {
|
|
71 stream = AudioSystem.getAudioInputStream(res.getStream());
|
|
72 }
|
|
73 catch (UnsupportedAudioFileException e) {
|
|
74 throw new IOException("Unsupported audio file format for '"+name+"'.\n");
|
|
75 }
|
|
76 catch (IOException e)
|
|
77 {
|
|
78 throw new IOException("Could not load audio resource '"+name+"'.\n");
|
|
79 }
|
|
80
|
|
81 format = stream.getFormat();
|
|
82
|
|
83 if (effect) {
|
|
84 System.out.print("Loading '"+name+"' as a clip\n");
|
|
85 try {
|
|
86 clip = AudioSystem.getClip();
|
|
87 clip.open(stream);
|
18
|
88 }
|
21
|
89 catch (LineUnavailableException e)
|
|
90 {
|
|
91 throw new IOException("Line unavailable for '"+name+"'.\n");
|
|
92 }
|
|
93 finally {
|
|
94 stream.close();
|
|
95 }
|
|
96 }
|
|
97 else
|
|
98 {
|
|
99 clip = null;
|
|
100 System.out.print("Loading '"+name+"' as stream\n");
|
|
101
|
18
|
102 try {
|
21
|
103 SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
|
|
104 System.out.print("info: "+stream.getFrameLength() + ", " + format.getFrameSize() + "\n");
|
|
105 line = (SourceDataLine) AudioSystem.getLine(info);
|
|
106 line.open(format);
|
|
107 }
|
|
108 catch (LineUnavailableException e) {
|
|
109 throw new IOException("Line unavailable for '"+name+"'.\n");
|
18
|
110 }
|
21
|
111 }
|
|
112 }
|
|
113
|
|
114 public boolean isClip()
|
|
115 {
|
|
116 return (clip != null && line == null);
|
|
117 }
|
|
118
|
|
119 public void play()
|
|
120 {
|
|
121 System.out.print("Sound("+name+").play()\n");
|
|
122 if (isClip())
|
|
123 {
|
|
124 clip.setFramePosition(0);
|
|
125 clip.start();
|
|
126 }
|
|
127 else
|
|
128 {
|
|
129 if (playThread == null)
|
18
|
130 {
|
21
|
131 doPlay = true;
|
|
132 loopCount = 1;
|
|
133 playThread = new Thread(this);
|
|
134 playThread.start();
|
18
|
135 }
|
|
136 }
|
|
137 }
|
|
138
|
|
139 public void loop(int n)
|
|
140 {
|
21
|
141 System.out.print("Sound("+name+").loop("+n+")\n");
|
|
142 if (isClip())
|
|
143 {
|
|
144 clip.setFramePosition(0);
|
|
145 if (n < 0)
|
|
146 clip.loop(Clip.LOOP_CONTINUOUSLY);
|
|
147 else
|
|
148 clip.loop(n);
|
|
149 }
|
18
|
150 else
|
21
|
151 {
|
|
152 if (playThread == null)
|
|
153 {
|
|
154 doPlay = true;
|
|
155 loopCount = n;
|
|
156 playThread = new Thread(this);
|
|
157 playThread.start();
|
|
158 }
|
|
159 }
|
18
|
160 }
|
|
161
|
|
162 public void stop()
|
|
163 {
|
21
|
164 if (isClip())
|
|
165 {
|
|
166 if (clip.isRunning())
|
|
167 clip.stop();
|
|
168 }
|
|
169 else
|
|
170 {
|
|
171 if (playThread != null)
|
|
172 {
|
|
173 playThread.interrupt();
|
|
174 doPlay = false;
|
|
175 playThread = null;
|
|
176 }
|
|
177 }
|
18
|
178 }
|
|
179
|
|
180 public boolean isPlaying()
|
|
181 {
|
21
|
182 if (isClip())
|
|
183 return clip.isRunning();
|
|
184 else
|
|
185 return (playThread != null && doPlay);
|
|
186 }
|
|
187
|
|
188 public void run()
|
|
189 {
|
|
190 line.start();
|
|
191 byte[] buf = new byte[line.getBufferSize()];
|
18
|
192
|
21
|
193 while (doPlay && (loopCount > 0 || loopCount == -1))
|
|
194 {
|
|
195 try {
|
|
196 int numRead = 0;
|
|
197 while ((numRead = stream.read(buf, 0, buf.length)) >= 0 && doPlay)
|
|
198 {
|
|
199 int offset = 0;
|
|
200 while (offset < numRead)
|
|
201 {
|
|
202 System.out.print("audioThread: offs="+offset+", numread="+numRead+"\n");
|
|
203 offset += line.write(buf, offset, numRead - offset);
|
|
204 }
|
|
205 }
|
|
206 line.drain();
|
|
207
|
|
208 System.out.print("audioThread: stream.reset()\n");
|
|
209 stream.reset();
|
|
210 }
|
|
211 catch (IOException e) {
|
|
212 }
|
|
213
|
|
214 if (loopCount > 0)
|
|
215 loopCount--;
|
|
216 }
|
|
217
|
|
218 line.stop();
|
|
219 doPlay = false;
|
18
|
220 }
|
|
221 }
|
|
222
|
21
|
223
|
1
|
224 class PathInfo
|
|
225 {
|
2
|
226 public int in, inX, inY, out, outX, outY;
|
1
|
227
|
2
|
228 public PathInfo(int in, int inX, int inY, int out, int outX, int outY)
|
|
229 {
|
|
230 this.in = in;
|
|
231 this.inX = inX;
|
|
232 this.inY = inY;
|
|
233
|
|
234 this.out = out;
|
|
235 this.outX = outX;
|
|
236 this.outY = outY;
|
|
237 }
|
1
|
238 }
|
0
|
239
|
8
|
240 /*
|
|
241 class AnimatedElement
|
|
242 {
|
|
243 float x, y, stime, value;
|
|
244 Interpolate lerp;
|
|
245 boolean active;
|
|
246
|
|
247 public AnimatedElement(float x, float y, )
|
|
248 {
|
|
249 stime = 0;
|
|
250 this.x = x;
|
|
251 this.y = y;
|
|
252
|
|
253 }
|
|
254
|
|
255 public animate(float time)
|
|
256 {
|
|
257 if (!active)
|
|
258 {
|
|
259 active = true;
|
|
260 stime = time;
|
|
261 }
|
|
262
|
|
263 float t = (time - stime) / 10.0f;
|
|
264 if (t < 100)
|
|
265 value = lerp.getValue(t);
|
|
266 else
|
|
267 {
|
|
268
|
|
269 }
|
|
270 }
|
|
271
|
|
272 public paint(Graphics2D g, );
|
|
273 {
|
|
274 }
|
|
275 }
|
|
276 */
|
0
|
277
|
1
|
278 class GameBoard
|
0
|
279 {
|
2
|
280 public static final int boardSize = 9;
|
|
281 public static final int boardMiddle = 4;
|
|
282 Piece[][] board;
|
|
283 Piece current;
|
8
|
284 public boolean flagGameOver;
|
7
|
285
|
|
286 int moveX, moveY, movePoint;
|
9
|
287
|
2
|
288 public GameBoard()
|
|
289 {
|
|
290 board = new Piece[boardSize][boardSize];
|
|
291
|
|
292 board[boardMiddle][boardMiddle] = new Piece(PieceType.START);
|
|
293
|
|
294 moveX = boardMiddle;
|
|
295 moveY = boardMiddle - 1;
|
7
|
296 movePoint = 0;
|
8
|
297
|
9
|
298 pieceFinishTurn();
|
8
|
299
|
|
300 flagGameOver = false;
|
2
|
301 }
|
0
|
302
|
7
|
303 public void paint(Graphics2D g, int sx, int sy, float scale)
|
2
|
304 {
|
|
305 for (int y = 0; y < boardSize; y++)
|
|
306 for (int x = 0; x < boardSize; x++)
|
|
307 if (board[x][y] != null)
|
|
308 {
|
6
|
309 AffineTransform save = g.getTransform();
|
|
310
|
4
|
311 board[x][y].paint(g,
|
|
312 sx + (x * scale),
|
|
313 sy + (y * scale),
|
|
314 scale - scale / 10);
|
6
|
315
|
|
316 g.setTransform(save);
|
2
|
317 }
|
|
318 }
|
9
|
319
|
|
320 public void animate(float time)
|
|
321 {
|
|
322 for (int y = 0; y < boardSize; y++)
|
|
323 for (int x = 0; x < boardSize; x++)
|
|
324 if (board[x][y] != null)
|
|
325 {
|
|
326 board[x][y].animate(time);
|
|
327 }
|
10
|
328
|
9
|
329 }
|
1
|
330
|
2
|
331 private boolean isEmpty(int x, int y)
|
|
332 {
|
|
333 return (x >= 0 && x < boardSize && y >= 0 && y < boardSize && board[x][y] == null);
|
|
334 }
|
1
|
335
|
2
|
336 private Piece getPiece(int x, int y)
|
|
337 {
|
|
338 if (x >= 0 && x < boardSize && y >= 0 && y < boardSize)
|
|
339 return board[x][y];
|
|
340 else
|
|
341 return null;
|
|
342 }
|
7
|
343
|
2
|
344 public void pieceRotate(boolean dir)
|
|
345 {
|
9
|
346 if (current != null)
|
|
347 current.rotate(dir);
|
2
|
348 }
|
1
|
349
|
8
|
350 public PathInfo resolvePath(int startX, int startY, int startPoint, boolean mark)
|
7
|
351 {
|
|
352 int x = startX, y = startY;
|
8
|
353 int point = -1;
|
7
|
354
|
|
355 Piece curr = getPiece(startX, startY);
|
|
356 if (curr == null)
|
|
357 return null;
|
9
|
358
|
|
359 /*
|
7
|
360 while (curr != null)
|
|
361 {
|
8
|
362 // curr.(true);
|
|
363 // elements.spawn("", );
|
7
|
364 }
|
9
|
365 */
|
|
366
|
7
|
367 return new PathInfo(startPoint, startX, startY, point, x, y);
|
|
368 }
|
|
369
|
2
|
370 public void pieceFinishTurn()
|
|
371 {
|
|
372 if (current != null)
|
7
|
373 {
|
6
|
374 current.setType(PieceType.LOCKED);
|
8
|
375 PathInfo i = resolvePath(moveX, moveY, movePoint, true);
|
7
|
376
|
|
377 if (i != null)
|
|
378 {
|
|
379 }
|
|
380 }
|
|
381
|
8
|
382 current = new Piece(PieceType.ACTIVE);
|
7
|
383 if (isEmpty(moveX, moveY))
|
2
|
384 {
|
8
|
385 board[moveX][moveY] = current;
|
|
386 }
|
|
387 else
|
|
388 {
|
|
389 PathInfo i = resolvePath(moveX, moveY, movePoint, true);
|
|
390 if (i != null)
|
|
391 board[moveX][moveY] = current;
|
|
392 else
|
|
393 flagGameOver = true;
|
2
|
394 }
|
1
|
395 }
|
|
396 }
|
|
397
|
|
398
|
9
|
399 public class Engine extends JPanel
|
18
|
400 implements Runnable, KeyListener, MouseListener
|
1
|
401 {
|
8
|
402 Thread animThread;
|
|
403 boolean animEnable = false;
|
4
|
404 GameBoard lauta = null;
|
9
|
405 BufferedImage lautaBG = null, lautaBGScaled = null;
|
|
406 Dimension oldDim;
|
8
|
407 float clock;
|
21
|
408 SoundElement[] sounds;
|
|
409
|
|
410 public SoundElement snd(Sound snd)
|
|
411 {
|
|
412 return sounds[snd.ordinal()];
|
|
413 }
|
1
|
414
|
9
|
415 public Engine()
|
2
|
416 {
|
9
|
417 BufferedImage img;
|
8
|
418 clock = 0;
|
4
|
419
|
9
|
420 System.out.print("Engine() constructor\n");
|
|
421
|
4
|
422 try
|
|
423 {
|
18
|
424 ResourceLoader res = new ResourceLoader("graphics/board.png");
|
|
425 lautaBG = ImageIO.read(res.getStream());
|
21
|
426
|
|
427 sounds = new SoundElement[16];
|
|
428 for (Sound s : Sound.values())
|
|
429 {
|
|
430 System.out.print(s +" = "+ s.ordinal() +"\n");
|
|
431 sounds[s.ordinal()] = new SoundElement("sounds/" + s.getName(), s.isEffect());
|
|
432 }
|
4
|
433 }
|
|
434 catch (IOException e)
|
|
435 {
|
18
|
436 /*
|
4
|
437 JOptionPane.showMessageDialog(null,
|
21
|
438 e.getMessage(),
|
4
|
439 "Initialization error",
|
|
440 JOptionPane.ERROR_MESSAGE);
|
18
|
441 */
|
21
|
442 System.out.print(e.getMessage());
|
18
|
443 }
|
|
444
|
|
445 lauta = new GameBoard();
|
|
446 addKeyListener(this);
|
|
447 addMouseListener(this);
|
|
448
|
|
449 // Get initial focus
|
|
450 if (!hasFocus())
|
|
451 {
|
|
452 System.out.print("Engine(): requesting focus\n");
|
|
453 requestFocus();
|
4
|
454 }
|
|
455
|
21
|
456 snd(Sound.MUSIC_GAME1).loop(-1);
|
|
457 // snd(Sound.PIECE_PLACED).loop(-1);
|
8
|
458 }
|
|
459
|
|
460 public void startThreads()
|
|
461 {
|
9
|
462 System.out.print("startThreads()\n");
|
8
|
463 if (animThread == null)
|
|
464 {
|
|
465 animThread = new Thread(this);
|
|
466 animEnable = true;
|
|
467 animThread.start();
|
|
468 }
|
|
469 }
|
|
470
|
|
471 public void stopThreads()
|
|
472 {
|
9
|
473 System.out.print("stopThreads()\n");
|
8
|
474 if (animThread != null)
|
|
475 {
|
|
476 animThread.interrupt();
|
|
477 animEnable = false;
|
|
478 animThread = null;
|
|
479 }
|
2
|
480 }
|
0
|
481
|
18
|
482 public void mousePressed(MouseEvent e) { }
|
|
483 public void mouseEntered(MouseEvent e) { }
|
|
484 public void mouseExited(MouseEvent e) { }
|
|
485 public void mouseReleased(MouseEvent e) { }
|
|
486
|
|
487 public void mouseClicked(MouseEvent e)
|
|
488 {
|
|
489 System.out.print("mouseClicked()\n");
|
|
490 if (!hasFocus())
|
|
491 {
|
|
492 System.out.print("requesting focus\n");
|
|
493 requestFocus();
|
|
494 }
|
|
495 }
|
|
496
|
9
|
497 public void paintComponent(Graphics g)
|
2
|
498 {
|
|
499 Graphics2D g2 = (Graphics2D) g;
|
10
|
500
|
|
501 // Use antialiasing when rendering the game elements
|
0
|
502 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
|
503 RenderingHints.VALUE_ANTIALIAS_ON);
|
4
|
504
|
10
|
505 // Rescale background if component size has changed
|
9
|
506 Dimension dim = getSize();
|
|
507 if (oldDim == null || !dim.equals(oldDim))
|
|
508 {
|
|
509 lautaBGScaled = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
|
|
510 Graphics2D gimg = lautaBGScaled.createGraphics();
|
|
511 gimg.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
|
512 RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
10
|
513
|
|
514 gimg.drawImage(lautaBG, 0, 0, dim.width, dim.height, null);
|
9
|
515 oldDim = dim;
|
|
516 System.out.print("scale changed\n");
|
|
517 }
|
|
518
|
10
|
519 // Background, pieces
|
9
|
520 g2.drawImage(lautaBGScaled, 0, 0, null);
|
7
|
521 lauta.paint(g2, 100, 150, 60);
|
9
|
522
|
10
|
523 // Other elements
|
0
|
524 }
|
|
525
|
9
|
526 public void keyTyped(KeyEvent e)
|
2
|
527 {
|
|
528 }
|
1
|
529
|
2
|
530 public void keyReleased(KeyEvent e)
|
|
531 {
|
|
532 }
|
1
|
533
|
9
|
534 public void keyPressed(KeyEvent e)
|
2
|
535 {
|
21
|
536 System.out.print("running "+ snd(Sound.MUSIC_GAME1) + "\n");
|
7
|
537 switch (e.getKeyCode())
|
2
|
538 {
|
7
|
539 case KeyEvent.VK_LEFT:
|
|
540 case KeyEvent.VK_UP:
|
2
|
541 lauta.pieceRotate(false);
|
|
542 break;
|
0
|
543
|
7
|
544 case KeyEvent.VK_RIGHT:
|
|
545 case KeyEvent.VK_DOWN:
|
2
|
546 lauta.pieceRotate(true);
|
|
547 break;
|
|
548
|
7
|
549 case KeyEvent.VK_ENTER:
|
2
|
550 lauta.pieceFinishTurn();
|
21
|
551 snd(Sound.PIECE_PLACED).stop();
|
|
552 snd(Sound.PIECE_PLACED).play();
|
2
|
553 break;
|
|
554 }
|
|
555 }
|
8
|
556
|
|
557 public void run()
|
|
558 {
|
|
559 while (animEnable)
|
|
560 {
|
|
561 clock++;
|
|
562
|
9
|
563 // System.out.print("clock=" + clock + "\n");
|
|
564
|
|
565 lauta.animate(clock);
|
|
566
|
10
|
567 if (clock % 2 == 1)
|
|
568 repaint();
|
9
|
569
|
|
570 try {
|
|
571 Thread.sleep(10);
|
|
572 }
|
|
573
|
|
574 catch (InterruptedException x) {
|
|
575 }
|
8
|
576 }
|
|
577 }
|
0
|
578 }
|