Skip to content

Commit 78799b5

Browse files
Merge pull request #120 from stefanhaustein/master
Use a generic demo for all display hats
2 parents 2a1c437 + 6fc2e36 commit 78799b5

10 files changed

Lines changed: 275 additions & 42 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ The following table the currently supported devices within this project:
5757
| A/D | I2C | (1) | [MCP4725 12 bit DAC](src/main/java/com/pi4j/devices/mcp4725/README.md)
5858
| HAT | | | [Raspberry SenseHat](src/main/java/com/pi4j/examples/hat/raspberry/sensehat/)
5959
| HAT | | | [Waveshare 14972 Mini Display Hat](src/main/java/com/pi4j/examples/hat/waveshare/waveshare14972/)
60+
| HAT | | | [Waveshare GamePi13 Hat](src/main/java/com/pi4j/examples/hat/waveshare/gamepi13/)
6061
| IO Expander | I2C | | [MCP23008 drive and read chip GPIOs](src/main/java/com/pi4j/devices/mcp23008/README.md)
6162
| IO Expander | I2C | | [MCP23008 and MCP23017 Pin monitoring (interrupt support)](src/main/java/com/pi4j/devices/mcp23xxxApplication/README.md)
6263
| IO Expander | I2C | | [MCP23017 drive and read chip GPIOs](src/main/java/com/pi4j/devices/mcp23017/README.md)
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package com.pi4j.examples.games.bricks;
2+
3+
import com.pi4j.drivers.display.graphics.GraphicsDisplay;
4+
import com.pi4j.drivers.input.GameController;
5+
import com.pi4j.io.ListenableOnOffRead;
6+
import com.pi4j.util.DeferredDelay;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.function.Consumer;
11+
12+
public class Bricks {
13+
14+
private static final int FIELD_SIZE = 64;
15+
private static final int BALL_SIZE = 4;
16+
private static final int PADDLE_WIDTH = 16;
17+
private static final int PADDLE_HEIGHT = 4;
18+
private static final int PADDLE_Y = FIELD_SIZE - PADDLE_HEIGHT * 3 / 2;
19+
20+
private final GraphicsDisplay display;
21+
private final GameController controller;
22+
private final DeferredDelay delay = new DeferredDelay();
23+
private final Map<ListenableOnOffRead<?>, Consumer<Boolean>> keys = new HashMap<>();
24+
25+
private final int scale;
26+
private final int x0;
27+
private final int y0;
28+
private final Map<ListenableOnOffRead<?>, Consumer<Boolean>> activeKeys = new HashMap<>();
29+
30+
private float ballX;
31+
private int ballY;
32+
private float ballDx;
33+
private int ballDy;
34+
35+
private int paddleX;
36+
private int paddleDx;
37+
private boolean exit;
38+
private boolean gameOver;
39+
private int stepTime = 25;
40+
41+
boolean[][] bricks = new boolean[4][3];
42+
int remainingBrickCount;
43+
44+
public Bricks(GraphicsDisplay display, GameController controller) {
45+
this.display = display;
46+
this.controller = controller;
47+
int displayWidth = display.getWidth();
48+
int displayHeight = display.getHeight();
49+
50+
assignKeys(on -> setMovement(on, -2), GameController.Key.LEFT, GameController.Key.LT);
51+
assignKeys(on -> setMovement(on, 2), GameController.Key.RIGHT, GameController.Key.RT);
52+
assignKeys(on -> actionKey(on), GameController.Key.CENTER, GameController.Key.A, GameController.Key.START);
53+
54+
scale = Math.min(displayHeight / FIELD_SIZE, displayWidth / FIELD_SIZE);
55+
x0 = (displayWidth - FIELD_SIZE * scale) / 2;
56+
y0 = (displayHeight - FIELD_SIZE * scale) / 2;
57+
58+
resetBall();
59+
initializeBricks();
60+
}
61+
62+
private void assignKeys(Consumer<Boolean> consumer, GameController.Key... keys) {
63+
for (GameController.Key key : keys) {
64+
ListenableOnOffRead<?> lor = controller.getKey(key);
65+
if (lor != null) {
66+
this.activeKeys.put(lor, lor.addConsumer(consumer));
67+
}
68+
}
69+
}
70+
71+
private void actionKey(boolean on) {
72+
if (!on && gameOver) {
73+
exit = true;
74+
} else if (on && ballDy == 0) {
75+
ballDy = -1;
76+
ballDx = 0.1f;
77+
}
78+
}
79+
80+
private void initializeBricks() {
81+
display.fillRect(0, 0, display.getWidth(), display.getHeight(), 0);
82+
for (int i = 0; i < 4; i++) {
83+
for (int j = 0; j < 3; j++) {
84+
setBrick(i, j, true);
85+
}
86+
}
87+
paddleX = (FIELD_SIZE - PADDLE_WIDTH) / 2;
88+
remainingBrickCount = 4 * 3;
89+
}
90+
91+
private void moveBall() {
92+
if (gameOver) {
93+
94+
} else if (ballDy == 0) {
95+
ballX = paddleX + (PADDLE_WIDTH-BALL_SIZE) / 2;
96+
} else {
97+
ballX += ballDx;
98+
ballY += ballDy;
99+
100+
if (ballX < 0 || ballX >= FIELD_SIZE - BALL_SIZE) {
101+
// Side reflection
102+
ballDx = -ballDx;
103+
ballX += ballDx;
104+
}
105+
106+
if (ballY < 0) {
107+
// Top reflection
108+
ballDy = -ballDy;
109+
ballY += ballDy;
110+
} else if (ballY + BALL_SIZE > PADDLE_Y) {
111+
// paddle or miss
112+
int paddleCenter = paddleX + PADDLE_WIDTH / 2;
113+
float paddleCenterDistance = ballX + BALL_SIZE / 2f - paddleCenter;
114+
float absolutePaddleCenterDistance = Math.abs(paddleCenterDistance);
115+
116+
if (absolutePaddleCenterDistance > (PADDLE_WIDTH + BALL_SIZE) / 2f) {
117+
gameOver = true;
118+
paddleDx = 0;
119+
} else {
120+
ballDy = -ballDy;
121+
ballY += ballDy;
122+
123+
ballDx += 2 * paddleCenterDistance / PADDLE_WIDTH;
124+
ballDx = Math.max(-1.2f, Math.min(ballDx, 1.2f));
125+
126+
if (remainingBrickCount == 0) {
127+
initializeBricks();
128+
if (stepTime > 10) {
129+
stepTime--;
130+
}
131+
}
132+
}
133+
} else {
134+
int row = ballY / 8;
135+
if (row >= 1 && row <= 3) {
136+
int i = (int) (ballX / 16);
137+
int j = row - 1;
138+
if (bricks[i][j]) {
139+
setBrick(i, j, false);
140+
ballDy = -ballDy;
141+
ballY += ballDy;
142+
remainingBrickCount--;
143+
}
144+
}
145+
}
146+
}
147+
}
148+
149+
private void resetBall() {
150+
ballDx = 0;
151+
ballDy = 0;
152+
ballY = PADDLE_Y - BALL_SIZE;
153+
ballX = paddleX + (8-BALL_SIZE) / 2;
154+
}
155+
156+
157+
private void releaseKeys() {
158+
for (Map.Entry<ListenableOnOffRead<?>, Consumer<Boolean>> entry : activeKeys.entrySet()) {
159+
entry.getKey().removeConsumer(entry.getValue());
160+
}
161+
activeKeys.clear();
162+
}
163+
164+
private void renderPaddle(boolean on) {
165+
display.fillRect(x0 + paddleX * scale, y0 + PADDLE_Y * scale, PADDLE_WIDTH * scale, PADDLE_HEIGHT * scale, on ? 0x888888 : 0);
166+
}
167+
168+
private void renderBall(boolean on) {
169+
display.fillRect(Math.round(x0 + ballX * scale), y0 + ballY * scale, BALL_SIZE * scale , BALL_SIZE * scale, on ? 0x888888 : 0);
170+
}
171+
172+
173+
public void run() {
174+
while (!exit) {
175+
delay.setDelayMillis(stepTime);
176+
renderPaddle(false);
177+
paddleX = Math.max(0, Math.min(paddleX + paddleDx, FIELD_SIZE - PADDLE_WIDTH));
178+
renderPaddle(true);
179+
180+
renderBall(false);
181+
moveBall();
182+
renderBall(true);
183+
184+
delay.materializeDelay();
185+
}
186+
releaseKeys();
187+
}
188+
189+
private void setMovement(boolean on, int dx) {
190+
if (gameOver) {
191+
if (on) {
192+
exit = true;
193+
}
194+
} else {
195+
paddleDx = on ? dx : 0;
196+
}
197+
}
198+
199+
private void setBrick(int x, int y, boolean alive) {
200+
bricks[x][y] = alive;
201+
display.fillRect(
202+
x0 + 16 * x * scale + 1,
203+
y0 + 8 * (y + 1) * scale + 1,
204+
14 * scale,
205+
6 * scale,
206+
alive ? (y == 0 ? 0xff0000 : y == 1 ? 0xff00 : 0xff) : 0);
207+
}
208+
}

src/main/java/com/pi4j/examples/games/snake/Snake.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public class Snake {
4141

4242
private Entity[][] arena;
4343
private List<Segment> body = new ArrayList<>();
44-
private long longPressStart = Long.MAX_VALUE;
4544
private boolean exit = false;
4645

4746
public Snake(GraphicsDisplay display, GameController controller) {
@@ -52,8 +51,6 @@ public Snake(GraphicsDisplay display, GameController controller) {
5251
assignKey(GameController.Key.DOWN, value -> processDirectionalKey(value, 0, 1));
5352
assignKey(GameController.Key.LEFT, value -> processDirectionalKey(value, -1, 0));
5453
assignKey(GameController.Key.RIGHT, value -> processDirectionalKey(value, 1, 0));
55-
assignKey(GameController.Key.CENTER, value -> processCenterKey(value));
56-
assignKey(GameController.Key.SELECT, _ -> { exit = true; });
5754

5855
int displayWidth = display.getWidth();
5956
int displayHeight = display.getHeight();
@@ -107,9 +104,6 @@ private void initialize() {
107104
addFood();
108105
}
109106

110-
private void processCenterKey(boolean pressed) {
111-
longPressStart = pressed ? System.currentTimeMillis() : Long.MAX_VALUE;
112-
}
113107

114108
private void processDirectionalKey(boolean keyPressed, int dx, int dy) {
115109
if (keyPressed) {
@@ -128,9 +122,6 @@ public void run() {
128122
deferredDelay.setDelayMillis(stepTimeMillis);
129123
step();
130124
deferredDelay.materializeDelay();
131-
if (System.currentTimeMillis() - longPressStart > 1000) {
132-
exit = true;
133-
}
134125
}
135126
display.fillRect(0, 0, display.getWidth(), display.getHeight(), 0xff000000);
136127
for (Map.Entry<ListenableOnOffRead<?>, Consumer<Boolean>> entry : keys.entrySet()) {
@@ -157,7 +148,7 @@ private void step() {
157148
}
158149
fadeOut -= stepTimeMillis;
159150
if (fadeOut <= 0) {
160-
initialize();
151+
exit = true;
161152
}
162153
} else {
163154
int newX = headX + dx;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.pi4j.examples.hat;
2+
3+
import com.pi4j.drivers.display.graphics.GraphicsDisplay;
4+
import com.pi4j.drivers.input.GameController;
5+
import com.pi4j.drivers.sensor.Sensor;
6+
import com.pi4j.examples.games.bricks.Bricks;
7+
import com.pi4j.examples.games.snake.Snake;
8+
import com.pi4j.examples.ui.ListView;
9+
import com.pi4j.examples.ui.SensorView;
10+
11+
import java.util.List;
12+
13+
/** A generic demo for display hats */
14+
public class DisplayHatDemo {
15+
16+
private final GraphicsDisplay display;
17+
private final GameController controller;
18+
private final List<Sensor> sensors;
19+
20+
public DisplayHatDemo(
21+
GraphicsDisplay display,
22+
GameController controller,
23+
List<Sensor> sensors
24+
) {
25+
this.display = display;
26+
this.controller = controller;
27+
this.sensors = sensors;
28+
}
29+
30+
public void run() {
31+
int resolution = Math.min(display.getWidth(), display.getHeight());
32+
int scale = Math.max(1, resolution) / 64;
33+
34+
ListView menu = new ListView(display, controller, scale);
35+
if (sensors != null && !sensors.isEmpty()) {
36+
menu.add("Sensors", () -> new SensorView(display, controller, scale)
37+
.addAll(sensors)
38+
.run());
39+
}
40+
menu.add("Snake", () -> new Snake(display, controller).run());
41+
if (resolution >= 64) {
42+
menu.add("Bricks", () -> new Bricks(display, controller).run());
43+
}
44+
menu.add("Exit", ListView.EXIT_ACTION);
45+
menu.run();
46+
}
47+
48+
49+
}

src/main/java/com/pi4j/examples/hat/raspberry/sensehat/SenseHatDemo.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import com.pi4j.Pi4J;
44
import com.pi4j.context.Context;
55
import com.pi4j.drivers.hat.raspberry.SenseHat;
6-
import com.pi4j.examples.games.snake.Snake;
7-
import com.pi4j.examples.ui.ListView;
8-
import com.pi4j.examples.ui.SensorView;
6+
import com.pi4j.examples.hat.DisplayHatDemo;
97

108
/**
119
* Demo for the Sense hat. Renders a menu with options for showing sensor values and playing snake.
@@ -16,13 +14,7 @@ public static void main(String[] args) {
1614
Context pi4j = Pi4J.newAutoContext();
1715
SenseHat senseHat = new SenseHat(pi4j);
1816

19-
ListView menu = new ListView(senseHat.getDisplay(), senseHat.getController())
20-
.add("Sensors", () -> new SensorView(senseHat.getDisplay(), senseHat.getController(), 1)
21-
.addAll(senseHat.getAllSensors())
22-
.run())
23-
.add("Snake", () -> new Snake(senseHat.getDisplay(), senseHat.getController()).run());
24-
25-
menu.run();
17+
new DisplayHatDemo(senseHat.getDisplay(), senseHat.getController(), senseHat.getAllSensors()).run();
2618

2719
pi4j.shutdown();
2820
}

src/main/java/com/pi4j/examples/hat/waveshare/gamepi13/Demo.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import com.pi4j.Pi4J;
44
import com.pi4j.context.Context;
5-
import com.pi4j.drivers.display.graphics.GraphicsDisplay;
65
import com.pi4j.drivers.hat.waveshare.GamePi13;
7-
import com.pi4j.drivers.input.GameController;
6+
import com.pi4j.examples.hat.DisplayHatDemo;
87

98
/**
109
* A simple demo running a "Snake" game. Exit by pressing the select key.
@@ -14,13 +13,11 @@ public class Demo {
1413
static void main(String[] args) {
1514
Context pi4j = Pi4J.newAutoContext();
1615
GamePi13 hat = new GamePi13(pi4j);
17-
GraphicsDisplay display = hat.getDisplay();
18-
GameController controller = hat.getController();
1916

20-
new com.pi4j.examples.games.snake.Snake(display, controller).run();
17+
new DisplayHatDemo(hat.getDisplay(), hat.getController(), null).run();
2118

22-
display.close();
23-
controller.close();
19+
hat.getDisplay().close();
20+
hat.getController().close();
2421
pi4j.shutdown();
2522
}
2623

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
# Waveshare GamePi13 Demo
22

3-
Runs a small snake game on the display.
4-
5-
Exit by pressing the select key.
3+
A small demo allowing the user to play two mini games.

0 commit comments

Comments
 (0)