Skip to content

Commit 955744d

Browse files
committed
Add Face-GameBoy Snake example
1 parent fdadced commit 955744d

File tree

7 files changed

+722
-0
lines changed

7 files changed

+722
-0
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/******************************************************************************
2+
* M5Snake : Game board management *
3+
* ------------------------------- *
4+
* Manage the game board (storage in memory and display *
5+
* Author: Olivier Staquet *
6+
* Last version available on https://github.com/ostaquet/M5Snake *
7+
*****************************************************************************/
8+
#include "GameBoard.h"
9+
10+
/**
11+
* Initialize
12+
*/
13+
void GameBoardClass::begin(uint8_t _max_game_cycles) {
14+
// Keep the number of game cycles
15+
max_game_cycles = _max_game_cycles;
16+
current_game_cycle = 0;
17+
18+
// Init the board at blank
19+
for(uint8_t x = 0; x < board_width; x++) {
20+
for(uint8_t y = 0; y < board_height; y++) {
21+
board_data[x][y] = BLOCK_STATUS_EMPTY;
22+
board_changes[x][y] = 0;
23+
}
24+
}
25+
26+
// Set screen to blank
27+
M5.Lcd.fillScreen(BLACK);
28+
}
29+
30+
/**
31+
* Refresh display
32+
*/
33+
void GameBoardClass::refresh() {
34+
// Check where there are some changes
35+
for(uint8_t x = 0; x < board_width; x++) {
36+
for(uint8_t y = 0; y < board_height; y++) {
37+
// Check the cell
38+
if(board_changes[x][y] > 0) {
39+
// There is a change...
40+
drawChange(x, y);
41+
// Reset the change tracking
42+
board_changes[x][y] = 0;
43+
}
44+
}
45+
}
46+
}
47+
48+
/**
49+
* Put the head of the snake on the board and go on right
50+
*/
51+
void GameBoardClass::startSnake() {
52+
// Define the middle of the screen
53+
setCell(board_width / 2, board_height / 2, BLOCK_STATUS_HEAD);
54+
55+
// Define the direction
56+
setDirection(DIRECTION_RIGHT);
57+
}
58+
59+
/**
60+
* Make the snake move on the board
61+
* Return boolean true if OK, false if game over
62+
*/
63+
bool GameBoardClass::moveSnake() {
64+
// Check if it is a cycle to move
65+
if(current_game_cycle < max_game_cycles) {
66+
// Wait for the next cycle
67+
current_game_cycle++;
68+
return true;
69+
} else {
70+
// Reset the game cycle
71+
current_game_cycle = 0;
72+
}
73+
74+
// Add 1 to all current block with between 1 and 512
75+
// to keep count of the movement of the snake (1 = head, 2 = 2nd block after head...)
76+
for(uint8_t x = 0; x < board_width; x++) {
77+
for(uint8_t y = 0; y < board_height; y++) {
78+
if(board_data[x][y] < BLOCK_STATUS_CHERRY && board_data[x][y] != BLOCK_STATUS_EMPTY) {
79+
board_data[x][y] = board_data[x][y] + 1;
80+
}
81+
}
82+
}
83+
84+
// Next move to be defined
85+
int8_t next_block_x = current_head_x;
86+
int8_t next_block_y = current_head_y;
87+
88+
// Define the next move of the head
89+
switch(current_direction) {
90+
case DIRECTION_UP :
91+
next_block_y = current_head_y - 1;
92+
break;
93+
case DIRECTION_RIGHT :
94+
next_block_x = current_head_x + 1;
95+
break;
96+
case DIRECTION_DOWN :
97+
next_block_y = current_head_y + 1;
98+
break;
99+
case DIRECTION_LEFT :
100+
next_block_x = current_head_x - 1;
101+
break;
102+
}
103+
104+
// Check if the move is valid...
105+
// Check the limit of the board for X
106+
if(next_block_x < 0 || next_block_x >= board_width) {
107+
return false;
108+
}
109+
// Check the limit of the board for Y
110+
if(next_block_y < 0 || next_block_y >= board_height) {
111+
return false;
112+
}
113+
114+
// Check if there is a cherry on the cell (if not a cherry, remove the last block of the tail
115+
if(board_data[next_block_x][next_block_y] != BLOCK_STATUS_CHERRY) {
116+
removeTail();
117+
}
118+
119+
// Check if there is another part of the snake
120+
if(board_data[next_block_x][next_block_y] > BLOCK_STATUS_EMPTY && board_data[next_block_x][next_block_y] < BLOCK_STATUS_CHERRY) {
121+
return false;
122+
}
123+
124+
// OK, move the head of the snake
125+
setCell(next_block_x, next_block_y, BLOCK_STATUS_HEAD);
126+
127+
return true;
128+
}
129+
130+
/**
131+
* Identify and remove the tail (last block of the snake)
132+
*/
133+
void GameBoardClass::removeTail() {
134+
uint16_t greatest_value = 0;
135+
uint8_t tail_x = 0;
136+
uint8_t tail_y = 0;
137+
138+
// Find the cell with the biggest value (it is the tail)
139+
for(uint8_t x = 0; x < board_width; x++) {
140+
for(uint8_t y = 0; y < board_height; y++) {
141+
if(board_data[x][y] < BLOCK_STATUS_CHERRY) {
142+
if(board_data[x][y] > greatest_value) {
143+
tail_x = x;
144+
tail_y = y;
145+
greatest_value = board_data[x][y];
146+
}
147+
}
148+
}
149+
}
150+
151+
// Remove the tail
152+
setCell(tail_x, tail_y, BLOCK_STATUS_EMPTY);
153+
}
154+
155+
/**
156+
* Get the max score
157+
*/
158+
uint16_t GameBoardClass::getMaxScore() {
159+
uint16_t greatest_value = 0;
160+
161+
// Find the cell with the biggest value (it is the tail)
162+
for(uint8_t x = 0; x < board_width; x++) {
163+
for(uint8_t y = 0; y < board_height; y++) {
164+
if(board_data[x][y] < BLOCK_STATUS_CHERRY) {
165+
if(board_data[x][y] > greatest_value) {
166+
greatest_value = board_data[x][y];
167+
}
168+
}
169+
}
170+
}
171+
172+
return greatest_value - 1;
173+
}
174+
175+
/**
176+
* Draw the change of one cell
177+
*/
178+
void GameBoardClass::drawChange(uint8_t x, uint8_t y) {
179+
// Compute box position
180+
uint16_t pos_x = x * BLOCK_SIZE;
181+
uint16_t pos_y = y * BLOCK_SIZE;
182+
183+
// Depending on the content of the cell, draw the box
184+
switch(board_data[x][y]) {
185+
case BLOCK_STATUS_EMPTY :
186+
M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
187+
break;
188+
189+
case BLOCK_STATUS_CHERRY :
190+
M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
191+
M5.Lcd.fillCircle(pos_x + BLOCK_SIZE / 2, pos_y + BLOCK_SIZE / 2, BLOCK_SIZE / 2 - 1, RED);
192+
break;
193+
194+
default :
195+
M5.Lcd.drawRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
196+
M5.Lcd.fillRect(pos_x + 1, pos_y + 1, BLOCK_SIZE - 2, BLOCK_SIZE - 2, WHITE);
197+
break;
198+
}
199+
}
200+
201+
/**
202+
* Add a ramdom cherry on the board
203+
*/
204+
void GameBoardClass::addCherry() {
205+
uint8_t pos_x = random(0, board_width);
206+
uint8_t pos_y = random(0, board_height);
207+
208+
while(board_data[pos_x][pos_y] != BLOCK_STATUS_EMPTY) {
209+
pos_x = random(0, board_width);
210+
pos_y = random(0, board_height);
211+
}
212+
213+
setCell(pos_x, pos_y, BLOCK_STATUS_CHERRY);
214+
}
215+
216+
/**
217+
* Set direction
218+
*/
219+
void GameBoardClass::setDirection(uint8_t direction) {
220+
current_direction = direction;
221+
}
222+
223+
/**
224+
* Set a value in a cell
225+
*/
226+
void GameBoardClass::setCell(uint8_t x, uint8_t y, uint16_t status) {
227+
board_data[x][y] = status;
228+
board_changes[x][y] = 1;
229+
230+
if(status == BLOCK_STATUS_HEAD) {
231+
current_head_x = x;
232+
current_head_y = y;
233+
}
234+
}
235+
236+
GameBoardClass GameBoard;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/******************************************************************************
2+
* M5Snake : Game board management *
3+
* ------------------------------- *
4+
* Manage the game board (storage in memory and display *
5+
* Author: Olivier Staquet *
6+
* Last version available on https://github.com/ostaquet/M5Snake *
7+
*****************************************************************************/
8+
#ifndef _GAMEBOARD_H_
9+
#define _GAMEBOARD_H_
10+
11+
#include <Arduino.h>
12+
#include <M5Stack.h>
13+
14+
#define LCD_WIDTH 320
15+
#define LCD_HEIGHT 240
16+
17+
#define BLOCK_SIZE 16
18+
19+
#define BLOCK_STATUS_EMPTY 0x00 // Empty block
20+
#define BLOCK_STATUS_HEAD 0x01 // Head of the snake
21+
// All values between 2 and 511 // Body of the snake, greatest value = tail
22+
#define BLOCK_STATUS_CHERRY 0x200 // Cherry to increase the size of the snake
23+
24+
#define DIRECTION_UP 0x01
25+
#define DIRECTION_RIGHT 0x02
26+
#define DIRECTION_DOWN 0x03
27+
#define DIRECTION_LEFT 0x04
28+
29+
class GameBoardClass {
30+
public:
31+
// Initialize
32+
void begin(uint8_t game_cycles = 4);
33+
34+
// Refresh display
35+
void refresh();
36+
37+
// Start the snake
38+
void startSnake();
39+
40+
// Make the snake move on the board
41+
// Return boolean true if OK, false if game over
42+
bool moveSnake();
43+
44+
// Set direction
45+
void setDirection(uint8_t direction);
46+
47+
// Add a ramdom cherry on the board
48+
void addCherry();
49+
50+
// Get the max score
51+
uint16_t getMaxScore();
52+
53+
private:
54+
// Variables
55+
56+
// Current direction
57+
uint8_t current_direction = 0x00;
58+
uint8_t current_head_x = 0x00;
59+
uint8_t current_head_y = 0x00;
60+
61+
// Cycles
62+
uint8_t max_game_cycles = 4;
63+
uint8_t current_game_cycle = 0;
64+
65+
// Keep track of the size of the board
66+
uint8_t board_width = LCD_WIDTH / BLOCK_SIZE;
67+
uint8_t board_height = LCD_HEIGHT / BLOCK_SIZE;
68+
69+
// Game board in memory
70+
uint16_t board_data[LCD_WIDTH / BLOCK_SIZE][LCD_HEIGHT / BLOCK_SIZE];
71+
72+
// Track change of the game board to optimize refresh
73+
uint8_t board_changes[LCD_WIDTH / BLOCK_SIZE][LCD_HEIGHT / BLOCK_SIZE];
74+
75+
76+
// Internal functions
77+
// Set a value in a cell
78+
void setCell(uint8_t x, uint8_t y, uint16_t status);
79+
80+
// Draw a cell with the change indicated
81+
void drawChange(uint8_t x, uint8_t y);
82+
83+
// Remove the tail of the snake (put the biggest cell at blank)
84+
void removeTail();
85+
};
86+
87+
extern GameBoardClass GameBoard;
88+
89+
#endif // _GAMEBOARD_H_
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/******************************************************************************
2+
* M5Snake : Input management from Gameboy faces *
3+
* --------------------------------------------- *
4+
* Management of input coming from the face Gameboy *
5+
* Author: Olivier Staquet *
6+
* Last version available on https://github.com/ostaquet/M5Snake *
7+
*****************************************************************************/
8+
#include "GameboyInput.h"
9+
10+
/**
11+
* Initialize
12+
*/
13+
void GameboyInputClass::begin(uint8_t _i2c_address, uint8_t _pin_int_face) {
14+
// Store local info
15+
i2c_address = _i2c_address;
16+
pin_int_face = _pin_int_face;
17+
18+
// Prepare the detection of activity
19+
pinMode(pin_int_face, INPUT_PULLUP);
20+
21+
// Init the I2C
22+
Wire.begin();
23+
}
24+
25+
/**
26+
* Check if button pressed and return which one
27+
*/
28+
uint8_t GameboyInputClass::getActivity() {
29+
// Check if there is activity on interrupt
30+
if(digitalRead(pin_int_face) == LOW) {
31+
// If yes, request 1 byte from the panel
32+
Wire.requestFrom(i2c_address, (uint8_t)1);
33+
34+
// Check if data on the I2C is available
35+
while (Wire.available()) {
36+
// Receive one byte as character
37+
uint8_t key_val = Wire.read();
38+
if(key_val != 0x00) {
39+
return key_val;
40+
}
41+
}
42+
}
43+
44+
// No activity to send back
45+
return GAMEBOY_KEY_NONE;
46+
}
47+
48+
GameboyInputClass GameboyInput;

0 commit comments

Comments
 (0)