C++ code for the FEH software development project
/************************************************/ | |
/* Names: Jake Osterhout, Russell Steadman */ | |
/* File: SDP_GAME.cpp */ | |
/* Instructor: PARKE 03:00 */ | |
/************************************************/ | |
// Include necessary libraries | |
#include <FEHIO.h> | |
#include <FEHUtility.h> | |
#include <FEHLCD.h> | |
#include <string.h> | |
#include <stdlib.h> | |
// Define screen dimensions and game map dimensions | |
#define SCRX 320 | |
#define SCRY 240 | |
#define MX 16 | |
#define MY 12 | |
#define COLORA 0xB574DB | |
#define COLORB 0xF23D30 | |
#define COLORC 0x45ECEF | |
class Character { | |
public: | |
Character(int x = 0, int y = 0, int color = COLORA) : x(x), y(y), xPrior(x), yPrior(y), color(color) { | |
direction = 0; | |
onOrange = false; | |
} | |
/* | |
Directions: | |
0 - North | |
1 - East | |
2 - South | |
3 - West | |
*/ | |
int direction; | |
int x; | |
int y; | |
int color; | |
int xPrior; | |
int yPrior; | |
bool onOrange; | |
}; | |
// Stores all of the changing information in the game | |
struct Memory { | |
/* | |
The map describes the position of walls, free space, ghosts, oranges, and pac-man | |
Codes: | |
0 - Free Space | |
1 - Wall | |
2 - Ghost | |
3 - Orange | |
4 - Pac-man | |
*/ | |
int map[MX][MY]; | |
// List of players with the highest scores | |
struct Player { | |
char name[30]; | |
int score; | |
} leaderboard[6]; | |
Character pacman; | |
Character ghosts[3]; | |
int points; | |
bool paused; | |
bool won; | |
bool ended; | |
}; | |
class Game { | |
public: | |
Game() { | |
const char players[6][15] = {"Dr. Parke", "Alex", "Derge", "Max", "Tara", "Kyle"}; | |
for (int i = 0; i < 6; i++) { | |
strcpy(store.leaderboard[i].name, players[i]); | |
store.leaderboard[i].score = 0; | |
} | |
} | |
void openRoute(int type); | |
void modCoord(int direction, int *x, int *y); | |
void renderCoord(int x, int y); | |
bool checkDirection(int direction, int x, int y); | |
bool onlyDirection(int direction, int x, int y); | |
struct Memory store; | |
}; | |
void displayMenu(Game *game); | |
void displayPlay(Game *game); | |
void displayResults(Game *game); | |
void displayBoard(Game *game); | |
void displayRules(Game *game); | |
void displayCredits(Game *game); | |
void renderMap(Game *game); | |
void moveGhosts(Game *game); | |
void movePacman(Game *game); | |
int absV(int num); | |
int main() { | |
// Set the long side as the x-axis | |
LCD.SetOrientation(FEHLCD::North); | |
srand(TimeNow()); | |
// Initialize the game object | |
Game game; | |
// Open the menu automatically | |
game.openRoute(0); | |
} | |
void Game::openRoute(int type) { | |
// Clear the screen and reset colors to default | |
LCD.Clear(); | |
LCD.SetFontColor(WHITE); | |
LCD.SetBackgroundColor(BLACK); | |
/* | |
Run certain functions based on the route | |
Route Types: | |
0 - Menu | |
1 - Play Game | |
2 - Game Session Results | |
3 - Leaderboard | |
4 - Instructions/Rules | |
5 - Credits | |
*/ | |
if (type == 0) { | |
displayMenu(this); | |
} else if (type == 1) { | |
displayPlay(this); | |
} else if (type == 2) { | |
displayResults(this); | |
} else if (type == 3) { | |
displayBoard(this); | |
} else if (type == 4) { | |
displayRules(this); | |
} else if (type == 5) { | |
displayCredits(this); | |
} | |
} | |
double absV(double num) { | |
if (num < 0) return num * -1; | |
return num; | |
} | |
// displayMenu renders the menu and responds to touches | |
void displayMenu(Game *game) { | |
// Set the number of options and text to show | |
int optNum = 5; | |
const char options[5][30] = {"How to Play FEHeck Yeah", "Play the Game", "Leaderboard", "Reset Stats", "Credits"}; | |
// Draw vertical bounding lines for the menu | |
for (int i = 0; i < optNum + 1; i++) { | |
LCD.FillRectangle(0, SCRY / optNum * i - 3, SCRX, 3); | |
LCD.FillRectangle(0, SCRY / optNum * i, SCRX, 3); | |
} | |
// Draw horizontal bounding lines for the menu | |
LCD.FillRectangle(0, 0, 6, SCRY); | |
LCD.FillRectangle(SCRX - 6, 0, 6, SCRY); | |
// Write out the options to the screen | |
for (int i = 0; i < optNum; i++) | |
LCD.WriteAt(options[i], SCRX / 2 - strlen(options[i]) / 2 * 12, SCRY * (i * 2. + 1.)/(optNum * 2.) - 8.); | |
// Wait for the user to touch something | |
while (true) { | |
// Record a touch and proceed after the finger has been removed | |
float xTemp, yTemp, y; | |
while (!LCD.Touch(&xTemp, &yTemp)); | |
y = yTemp; | |
while (LCD.Touch(&xTemp, &yTemp)); | |
// Determine which option the user touched | |
int selected = (y / SCRY * optNum); | |
if (selected == 0) { | |
return game->openRoute(4); | |
} else if (selected == 1) { | |
return game->openRoute(1); | |
} else if (selected == 2) { | |
return game->openRoute(3); | |
} else if (selected == 3) { | |
for (int i = 0; i < 6; i++) game->store.leaderboard[i].score = 0; | |
return game->openRoute(3); | |
} else if (selected == 4) { | |
return game->openRoute(5); | |
} | |
} | |
} | |
// displayPlay renders the game, responds to user interaction, and time-based changes | |
void displayPlay(Game *game) { | |
// Set up the map using the specification listed in the Memory struct | |
int defaultMap[MY][MX] = { | |
{0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0}, | |
{0,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0}, | |
{0,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0}, | |
{1,1,1,0,1,0,0,0,1,0,1,0,0,0,1,0}, | |
{0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0}, | |
{0,1,0,1,1,0,1,0,0,0,0,0,1,0,1,0}, | |
{0,1,0,1,1,0,1,0,1,0,1,1,1,0,1,0}, | |
{0,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0}, | |
{0,1,0,1,1,0,0,0,0,0,1,1,1,1,1,1}, | |
{0,0,0,0,0,0,1,0,1,0,1,1,1,1,1,1}, | |
{1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1}, | |
{1,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1} | |
}; | |
for (int x = 0; x < MX; x++) { | |
for (int y = 0; y < MY; y++) { | |
// Copy the default into memory | |
game->store.map[x][y] = defaultMap[y][x]; | |
// Each free space has a 40% chance of containing an orange | |
if (game->store.map[x][y] == 0 && rand() % 5 < 2) { | |
game->store.map[x][y] = 3; | |
} | |
} | |
} | |
// Initialize all of the characters | |
Character pacman(15, 0, YELLOW); | |
Character ghost1(0, 0, COLORA); | |
Character ghost2(1, 11, COLORB); | |
Character ghost3(9, 8, COLORC); | |
// Store the characters to memory | |
game->store.pacman = pacman; | |
game->store.ghosts[0] = ghost1; | |
game->store.ghosts[1] = ghost2; | |
game->store.ghosts[2] = ghost3; | |
// Initialize game state variables | |
game->store.points = 0; | |
game->store.paused = false; | |
game->store.won = false; | |
game->store.ended = false; | |
// Initialize the time and touch variables | |
double start = TimeNow(); | |
float tX, tY, Xi, Yi, Xf, Yf; | |
// Change the UI to reflect the map in memory | |
renderMap(game); | |
while (true) { | |
// Stop if the game has ended | |
if (game->store.ended) return; | |
// Wait for a user touch or a game tick | |
while (!LCD.Touch(&tX, &tY) && (TimeNow() - start < 0.5 || game->store.paused)); | |
// If the event was a user touch | |
if (TimeNow() - start >= 0.5 && !game->store.paused) { | |
// Reset the start time | |
start = TimeNow(); | |
// Move pacman in the specified direction | |
movePacman(game); | |
// Move the ghosts randomly | |
moveGhosts(game); | |
// Check if oranges remain on the board | |
bool hasOranges = false; | |
// Does the board have oranges | |
for (int x = 0; x < MX; x++) { | |
for (int y = 0; y < MY; y++) { | |
if (game->store.map[x][y] == 3) { | |
hasOranges = true; | |
break; | |
} | |
} | |
if (hasOranges) break; | |
} | |
// Are the ghosts on top of oranges | |
for (int i; i < 3; i++) { | |
if (game->store.ghosts[i].onOrange) hasOranges = true; | |
} | |
// If there are no oranges, then the player wins | |
if (!hasOranges) { | |
game->store.ended = true; | |
game->store.won = true; | |
return game->openRoute(2); | |
} | |
} else if (LCD.Touch(&tX, &tY)) { | |
// Set the initial touch position | |
Xi = tX; | |
Yi = tY; | |
while (LCD.Touch(&tX, &tY)) { | |
if (tX != -1) { | |
// Set the final touch position | |
Xf = tX; | |
Yf = tY; | |
} | |
} | |
/* | |
Detect swipes via displacement of initial & final touch | |
Conditions: | |
* Initial touch on map | |
* Swipe must be at least 10 px | |
*/ | |
if ((Xi < 200 || Yi < 160) && (absV(Xf - Xi) >= 10 || absV(Yf - Yi) >= 10)) { | |
if (absV(Xf - Xi) > absV(Yf - Yi)) { | |
if (Xf - Xi > 0) { | |
game->store.pacman.direction = 1; // East | |
} else { | |
game->store.pacman.direction = 3; // West | |
} | |
} else { | |
if (Yf - Yi > 0) { | |
game->store.pacman.direction = 2; // South | |
} else { | |
game->store.pacman.direction = 0; // North | |
} | |
} | |
} | |
/* | |
Detect 6 buttons at the bottom of the screen | |
*/ | |
if (Xi > 200 && Xi <= 240) { | |
if (Yi > 160 && Yi <= 200) { | |
// Toggle pause | |
game->store.paused = !game->store.paused; | |
renderMap(game); | |
} else if (Yi > 200) { | |
// Left | |
game->store.pacman.direction = 3; | |
} | |
} else if (Xi > 240 && Xi <= 280) { | |
if (Yi > 160 && Yi <= 200) { | |
// Up | |
game->store.pacman.direction = 0; | |
} else if (Yi > 200) { | |
// Down | |
game->store.pacman.direction = 2; | |
} | |
} else if (Xi > 280) { | |
if (Yi > 160 && Yi <= 200) { | |
// Stop | |
game->store.ended = true; | |
return game->openRoute(2); | |
} else if (Yi > 200) { | |
// Right | |
game->store.pacman.direction = 1; | |
} | |
} | |
} | |
} | |
} | |
// renderMap renders the entire game map | |
void renderMap(Game *game) { | |
/* | |
Changes each map square based on its type | |
* All map squares are 20px by 20px | |
*/ | |
for (int x = 0; x < MX; x++) { | |
for (int y = 0; y < MY; y++) { | |
game->renderCoord(x, y); | |
} | |
} | |
// Draw the buttons at the bottom of the screen | |
LCD.SetBackgroundColor(WHITE); | |
LCD.SetFontColor(BLACK); | |
if (game->store.paused) LCD.WriteAt("PLY", 202, 172); | |
if (!game->store.paused) LCD.WriteAt("PSE", 202, 172); | |
LCD.WriteAt("UP", 248, 172); | |
LCD.WriteAt("END", 282, 172); | |
LCD.WriteAt("LFT", 202, 212); | |
LCD.WriteAt("DWN", 242, 212); | |
LCD.WriteAt("RGT", 282, 212); | |
LCD.FillRectangle(200, 199, 120, 2); | |
LCD.FillRectangle(239, 160, 2, 80); | |
LCD.FillRectangle(279, 160, 2, 80); | |
} | |
// renderCoord renders a single map coordinate | |
void Game::renderCoord(int x, int y) { | |
if (store.map[x][y] == 0) { | |
// Draw a black box for free space | |
LCD.SetFontColor(BLACK); | |
LCD.FillRectangle(x * 20, y * 20, 20, 20); | |
} else if (store.map[x][y] == 1) { | |
// Draw a white box for a wall | |
LCD.SetFontColor(WHITE); | |
LCD.FillRectangle(x * 20, y * 20, 20, 20); | |
} else if (store.map[x][y] == 2) { | |
// Draw a ghost | |
LCD.SetFontColor(BLACK); | |
LCD.FillRectangle(x * 20, y * 20, 20, 20); | |
LCD.SetFontColor(COLORA); | |
for (int i = 0; i < 3; i++) { | |
if (store.ghosts[i].x == x && store.ghosts[i].y == y) { | |
LCD.SetFontColor(store.ghosts[i].color); | |
} | |
} | |
LCD.FillRectangle(x * 20 + 2, y * 20 + 2, 16, 16); | |
} else if (store.map[x][y] == 3) { | |
// Draw an orange | |
LCD.SetFontColor(BLACK); | |
LCD.FillRectangle(x * 20, y * 20, 20, 20); | |
LCD.SetFontColor(ORANGE); | |
LCD.FillCircle(x * 20 + 10, y * 20 + 10, 5); | |
} else if (store.map[x][y] == 4) { | |
// Draw pacman | |
LCD.SetFontColor(BLACK); | |
LCD.FillRectangle(x * 20, y * 20, 20, 20); | |
LCD.SetFontColor(YELLOW); | |
LCD.FillCircle(x * 20 + 10, y * 20 + 10, 8); | |
} | |
} | |
// Modify the coordinates based on the direction | |
void Game::modCoord (int direction, int *x, int *y) { | |
if (direction == 0) *y = *y - 1; // North | |
if (direction == 1) *x = *x + 1; // East | |
if (direction == 2) *y = *y + 1; // South | |
if (direction == 3) *x = *x - 1; // West | |
} | |
// Check if the direction is off the board or a wall | |
bool Game::checkDirection(int direction, int x, int y) { | |
modCoord(direction, &x, &y); | |
if (x < 0 || y < 0 || y == MY || x == MX) return false; // Off screen | |
if (store.map[x][y] == 1) return false; // Wall | |
return true; | |
} | |
// Check if the backwards direction is the only direction | |
bool Game::onlyDirection(int direction, int x, int y) { | |
int dirOptions = 0; | |
// Loop through each direction to check | |
for (int i = 0; i < 4; i++) { | |
if (checkDirection(i, x, y)) dirOptions++; | |
} | |
if (dirOptions == 1) return true; | |
return false; | |
} | |
// moveGhosts randomly moves the ghosts to new positions | |
void moveGhosts(Game *game) { | |
// Loop through each ghost | |
for (int i = 0; i < 3; i++) { | |
int direction, y, x, yP = game->store.ghosts[i].yPrior, xP = game->store.ghosts[i].xPrior; | |
do { | |
// Generate a random direction 0 to 3 | |
direction = rand() % 4; | |
// Reset the x and y coordinates to the current position | |
y = game->store.ghosts[i].y; | |
x = game->store.ghosts[i].x; | |
// Modify the x and y to the new coordinates | |
game->modCoord(direction, &x, &y); | |
// If the direction is off screen or to a wall, regenerate a direction | |
if (!game->checkDirection(direction, game->store.ghosts[i].x, game->store.ghosts[i].y)) continue; | |
// If the direction is backwards and it is not the only direction, regenerate | |
if ((x == xP && y == yP) && !game->onlyDirection(direction, x, y)) continue; | |
break; | |
} while (true); | |
// The current position is now the prior | |
yP = game->store.ghosts[i].y; | |
xP = game->store.ghosts[i].x; | |
// Update the memory variables | |
game->store.ghosts[i].yPrior = yP; | |
game->store.ghosts[i].xPrior = xP; | |
game->store.ghosts[i].y = y; | |
game->store.ghosts[i].x = x; | |
// Remove the ghost from the prior position | |
if (game->store.ghosts[i].onOrange) { | |
game->store.map[xP][yP] = 3; | |
} else if (game->store.map[xP][yP] == 2) { | |
game->store.map[xP][yP] = 0; | |
} | |
game->renderCoord(xP, yP); | |
// Add the ghost to the new position | |
if (game->store.map[x][y] == 0) { | |
game->store.ghosts[i].onOrange = false; | |
game->store.map[x][y] = 2; | |
} else if (game->store.map[x][y] == 3) { | |
game->store.ghosts[i].onOrange = true; | |
game->store.map[x][y] = 2; | |
} else if (game->store.map[x][y] == 4) { | |
// Handle game over | |
game->store.ended = true; | |
return game->openRoute(2); | |
} | |
game->renderCoord(x, y); | |
} | |
} | |
// movePacman updates Pac-Man's location regularly | |
void movePacman(Game *game) { | |
int direction = game->store.pacman.direction, xP = game->store.pacman.x, yP = game->store.pacman.y, x, y; | |
// The x and y positions are the current ones | |
x = xP; | |
y = yP; | |
// If the new position is valid, change the coordinates | |
if (game->checkDirection(direction, x, y)) game->modCoord(direction, &x, &y); | |
// Store the new positions to memory | |
game->store.pacman.x = x; | |
game->store.pacman.y = y; | |
// Remove pacman from the prior position | |
game->store.map[xP][yP] = 0; | |
game->renderCoord(xP, yP); | |
// Add the ghost to the new position | |
if (game->store.map[x][y] == 0) { | |
game->store.map[x][y] = 4; | |
} else if (game->store.map[x][y] == 3) { | |
game->store.points++; | |
game->store.map[x][y] = 4; | |
} else if (game->store.map[x][y] == 2) { | |
// Handle game over | |
game->store.ended = true; | |
return game->openRoute(2); | |
} | |
game->renderCoord(x, y); | |
} | |
// displayResults renders the results of the game | |
void displayResults(Game *game) { | |
LCD.SetFontColor(BLACK); | |
LCD.FillRectangle(0, 0, SCRX, SCRY); | |
// Display text based on game outcome | |
LCD.SetFontColor(WHITE); | |
if (game->store.won) { | |
LCD.WriteAt("Flex on 'em! You won.", SCRX / 2 - 126, 40); | |
} else { | |
LCD.WriteAt("Rats! You lost.", SCRX / 2 - 90, 40); | |
} | |
// Display the number of points earned | |
LCD.WriteAt("Points Earned:", SCRX / 2 - 84, 80); | |
LCD.WriteAt(game->store.points, SCRX / 2 - 12, 105); | |
// Prompt the user to pick a character | |
LCD.WriteAt("Give points to:", SCRX / 2 - 90, 145); | |
char *lecture, *lab; | |
// Write all of the professors & TAs to the screen | |
for (int i = 0; i < 3; i++) { | |
lecture = game->store.leaderboard[i].name; | |
lab = game->store.leaderboard[i + 3].name; | |
LCD.WriteAt(lecture, SCRX * (i * 2. + 1.) / 6. - strlen(lecture) / 2 * 12, 172); | |
LCD.WriteAt(lab, SCRX * (i * 2. + 1.) / 6. - strlen(lab) / 2 * 12, 212); | |
} | |
while (true) { | |
// Record a touch and proceed after the finger has been removed | |
float xTemp, yTemp, x, y; | |
while (!LCD.Touch(&xTemp, &yTemp)); | |
x = xTemp; | |
y = yTemp; | |
while (LCD.Touch(&xTemp, &yTemp)); | |
// Detect pressing the professors and TAs, then proceed to the leaderboard | |
if (x <= 107) { | |
if (y > 160 && y <= 200) { | |
game->store.leaderboard[0].score += game->store.points; | |
return game->openRoute(3); | |
} else if (y > 200) { | |
game->store.leaderboard[3].score += game->store.points; | |
return game->openRoute(3); | |
} | |
} else if (x <= 214) { | |
if (y > 160 && y <= 200) { | |
game->store.leaderboard[1].score += game->store.points; | |
return game->openRoute(3); | |
} else if (y > 200) { | |
game->store.leaderboard[4].score += game->store.points; | |
return game->openRoute(3); | |
} | |
} else { | |
if (y > 160 && y <= 200) { | |
game->store.leaderboard[2].score += game->store.points; | |
return game->openRoute(3); | |
} else if (y > 200) { | |
game->store.leaderboard[5].score += game->store.points; | |
return game->openRoute(3); | |
} | |
} | |
} | |
} | |
// displayBoard renders the leaderboard | |
void displayBoard(Game *game) { | |
// Display title and instructions | |
LCD.WriteAt("Leaderboard", SCRX / 2 - 66, 7); | |
LCD.WriteAt("**Tap to continue**", SCRX / 2 - 114, 25); | |
// Display the name and points of each professor and TA | |
for (int i = 0; i < 6; i++) { | |
LCD.WriteAt(game->store.leaderboard[i].name, 40, 67 + 30 * i); | |
LCD.WriteAt(game->store.leaderboard[i].score, 180, 67 + 30 * i); | |
} | |
// Proceed after the finger has been removed | |
float x, y; | |
while (!LCD.Touch(&x, &y)); | |
while (LCD.Touch(&x, &y)); | |
// Open the menu screen | |
return game->openRoute(0); | |
} | |
// displayRules explains how to play | |
void displayRules(Game *game) { | |
int page = 0; | |
while (true) { | |
LCD.Clear(); | |
LCD.WriteLine("FEHeck Yeah Guide"); | |
LCD.WriteLine("**Tap to continue**"); | |
LCD.WriteLine(" "); | |
switch (page) { | |
case 0: | |
LCD.WriteLine("Goal:"); | |
LCD.WriteLine("* Collect all oranges"); | |
LCD.WriteLine(" "); | |
LCD.WriteLine("Rules:"); | |
LCD.WriteLine("* Don't touch ghosts"); | |
LCD.WriteLine("* Manuver around walls"); | |
break; | |
case 1: | |
LCD.WriteLine("Instructions:"); | |
LCD.WriteLine("* Tap UP to go up"); | |
LCD.WriteLine("* Tap DWN to go down"); | |
LCD.WriteLine("* Tap RGT to go right"); | |
LCD.WriteLine("* Tap LEFT to go left"); | |
LCD.WriteLine("* Tap PSE to pause"); | |
LCD.WriteLine("* Tap PLY to play"); | |
LCD.WriteLine("* Tap END to end game"); | |
break; | |
case 2: | |
LCD.WriteLine("Instructions (cont'):"); | |
LCD.WriteLine("* You can also swipe"); | |
LCD.WriteLine(" on the map to change"); | |
LCD.WriteLine(" direction"); | |
break; | |
default: | |
return game->openRoute(0); | |
} | |
// Proceed after the finger has been removed | |
float x, y; | |
while (!LCD.Touch(&x, &y)); | |
while (LCD.Touch(&x, &y)); | |
// Increment the page | |
page++; | |
} | |
} | |
// displayCredits attributes developers | |
void displayCredits(Game *game) { | |
int page = 0; | |
while (true) { | |
LCD.Clear(); | |
LCD.WriteLine("FEHeck Yeah Credits"); | |
LCD.WriteLine("**Tap to continue**"); | |
LCD.WriteLine(" "); | |
if (page == 0) { | |
LCD.WriteLine("Developers:"); | |
LCD.WriteLine("* Jake Osterhout"); | |
LCD.WriteLine("* Russell Steadman"); | |
LCD.WriteLine(" "); | |
LCD.WriteLine("References:"); | |
LCD.WriteLine("* u.osu.edu/fehproteus"); | |
LCD.WriteLine("* Pac-Man by BANDAI"); | |
LCD.WriteLine(" NAMCO Entertainment"); | |
} else { | |
return game->openRoute(0); | |
} | |
// Proceed after the finger has been removed | |
float x, y; | |
while (!LCD.Touch(&x, &y)); | |
while (LCD.Touch(&x, &y)); | |
// Increment the page | |
page++; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment