Files
tron.asm/src/game.c

501 lines
19 KiB
C

#include <lcd.h>
#include <lcd_lld.h>
#include <color.h>
#include <stdio.h>
#include "game.h"
#include "io.h"
#include "draw.h"
#include "bitmap.h" //generated by gimp
void game_init(game_t* game, uint16_t ticks_per_sec) {
// gpio init
io_init();
// lcd init
LCD_Init();
LCD_Clear(GUI_COLOR_BLACK);
// struct init
game->state=init;
game->ticks_per_pixel = SPEED_SLOW;
game->ticks_leftover = 0;
game->ticks_per_sec = ticks_per_sec;
game->time = 0;
game->ticks_sum_sec = 0;
}
bool game_check_line_collision(player_t* player, point_t start, point_t end, uint8_t pixels){
bool line_is_horizontal = (start.y == end.y);
bool player_is_horizontal = (player->direction == left || player->direction == right);
if(line_is_horizontal == player_is_horizontal){ // if player moves parallel to the line, there is no point in checking
return false; // no collision possible because parallel moving
}
if(!player_is_horizontal && (player->position.x < min(start.x, end.x) || player->position.x > max(start.x, end.x))){
return false; // if player is passing by horizontally, no collision possible
}
if(player_is_horizontal && (player->position.y < min(start.y, end.y) || player->position.y > max(start.y, end.y))){
return false; // if player is passing by vertically, no collision possible
}
switch(player->direction){
case up:
if(player->position.y > start.y && ((int16_t)player->position.y - pixels) <= start.y) {
return true; // going up and hitting a line segment
}
break;
case down:
if(player->position.y < start.y && ((int16_t)player->position.y + pixels) >= start.y) {
return true; // going down and hitting a line segment
}
break;
case left:
if(player->position.x > start.x && ((int16_t)player->position.x - pixels) <= start.x) {
return true; // going left and hitting a line segment
}
break;
case right:
if(player->position.x < start.x && ((int16_t)player->position.x + pixels) >= start.x) {
return true; // going right and hitting a line segment
}
break;
}
return false;
}
bool game_check_boundary_collision(game_t* game, player_t* player, uint8_t pixels){
// Check boundary collision
switch(player->direction){
case up:
if((int16_t)(player->position.y) - pixels <= TG_FIELD_TOP){
return true; // Collision at top boundary
}
break;
case down:
if((int16_t)(player->position.y) + pixels >= (TFT_HEIGHT - TG_FIELD_BOTTOM - 1)){
return true; // Collision at bottom boundary
}
break;
case left:
if((int16_t)(player->position.x) - pixels <= TG_FIELD_LEFT){
return true; // Collision at left boundary
}
break;
case right:
if((int16_t)(player->position.x) + pixels >= (TFT_WIDTH - TG_FIELD_RIGHT - 1)){
return true; // Collision at right boundary
}
break;
}
return false;
}
bool game_check_player_collision(game_t* game, player_t* player, uint8_t pixels){
// Check for collisions with players (including self)
for(int i = 0; i < PLAYER_COUNT; i++){
player_t* colliding = &(game->player[i]); // pointer to player whose lines we want to check (against opponent or self)
point_t last_point = colliding->past_positions[0]; // start point of the line
// For each line segment (last_point to curr_point)
for(int j = 1; j < colliding->num_positions; j++ ){
point_t curr_point = colliding->past_positions[j]; // end point of the line
if(game_check_line_collision(player, last_point, curr_point, pixels)){ // check if player collides with line segment
return true;
}
last_point = curr_point; // set new start point
}
if( player != colliding && // do not check against yourself
game_check_line_collision(player, last_point, colliding->position, pixels)){ // check if player collides with newest segment of opponent
return true;
}
}
return false;
}
bool game_check_collision(game_t* game, player_t* player, uint8_t pixels){ // Check boundary and player collisions
// Check for collisions with boundarys
if(game_check_boundary_collision(game, player, pixels)){
return true;
}
// Check for collisions with players (including self)
if(game_check_player_collision(game, player, pixels)){
return true;
}
return false; // no collision!
}
bool game_player_update(game_t* game, player_t* player, uint8_t pixels){
bool direction_change = false;
bool state_changed = false;
// Check for button presses
if(io_button_has_edge(player->btn_left)) { // If left button is pressed
player->direction= (player->direction + (4 - 1)) % 4 ; // Decrement direction value (counterclockwise)
direction_change = true;
} else if(io_button_has_edge(player->btn_right)) { // If right button is pressed
player->direction= (player->direction + 1) % 4 ; // Increment direction value (clockwise)
direction_change = true;
}
// Check if player is alive
if(player->state != alive){
return state_changed; // If player is dead return state
}
// Change direction
if(direction_change) {
player_append_position(player,player->position); // Append new position if direction has changed
}
if(pixels) {
if(game_check_collision(game, player, pixels)){ // Check if a collision is about to happen
player->state = dead; // If a collision is happening kill the player
state_changed = true; // return the state
}
point_t last_point = player->past_positions[player->num_positions-1]; // Get the players newest point
switch(player->direction) { // Get the players moving direction and render his move
case down: // render down
player->position.y+=pixels;
LCD_DrawRectF( player->position.x,
last_point.y,
PLAYER_WIDTH,
player->position.y - last_point.y,
player->color);
break;
case left: // render left
player->position.x-=pixels;
LCD_DrawRectF( player->position.x,
player->position.y,
last_point.x -player->position.x,
PLAYER_WIDTH,
player->color);
break;
case up: // render up
player->position.y-=pixels;
LCD_DrawRectF( player->position.x,
player->position.y,
PLAYER_WIDTH,
last_point.y - player->position.y,
player->color);
break;
case right: // render right
player->position.x+=pixels;
LCD_DrawRectF( last_point.x,
player->position.y,
player->position.x - last_point.x,
PLAYER_WIDTH,
player->color);
break;
}
}
return state_changed; // return state
}
void game_get_color(uint16_t confbits, uint16_t* player1_color, uint16_t* player2_color) {
//we have 4 bits per player color, we use 1 bit for red, 2 for green, 1 for blue
//Display color is 16 bit: 5bits red (15-11), 6 green (10-5), 5 blue (4-0)
*player1_color =0;
*player2_color =0;
uint16_t temp;
if(confbits&0x80) { //player1, red1
*player1_color|=0xF800; //set full red
}
temp = (confbits&0x60) << 1; //move the player1, green1-2 bits to the highest bit position (bits 7-6)
*player1_color|= temp << 3; //move green into the right position (bits 10-9)
if(confbits&0x10) { //player 1, blue1
*player1_color|=0x1F; //set full blue
}
if(*player1_color==0) {
*player1_color= (3 << 11) | (7 << 5) | (3 << 0); //set color to gray (2 bits active per color, or 3 bits for green)
}
if(confbits&0x08) { //player2, red1
*player2_color|=0xF800; //set full red
}
temp = (confbits&0x06) << 5; //move the player2, green1-2 bits to the highest bit position (bits 7-6)
*player2_color|= temp << 3; //move green into the right position (bits 10-9)
if(confbits&0x01) { //player 2, blue1
*player2_color|=0x1F; //set full blue
}
if(*player2_color==0) {
*player2_color= (3 << 11) | (7 << 5) | (3 << 0); //set color to gray (2 bits active per color, or 3 bits for green)
}
}
bool game_step_init(game_t* game) {
//Draw welcome bitmap
bitmap_draw(gimp_image.width,gimp_image.height,gimp_image.bytes_per_pixel,gimp_image.pixel_data);
//Wait on player to press the start button
while(!io_button_has_edge(BTN_START));
//Change game state
game->state=prestart;
LCD_Clear(GUI_COLOR_BLACK); // Clear the background
return true;
}
static const char* texts [] = {
"Config Instructions:",
"* Change the player colors using the switches S7-S0",
"* Use the poti to change the game speed",
"* Press T0 to start the game",
"",
"Game Instructions:",
"* Player 1 Keys: T3 and T2",
"* Player 2 Keys: T1 and T0",
"* Stay alive!",
NULL
};
bool game_step_prestart(game_t* game) {
//Draw "Player x: Color" Strings
for(int i=0; i<PLAYER_COUNT; i++) {
static char buf[16]; // Text buffer
LCD_SetTextColor(GUI_COLOR_WHITE);
sprintf(buf, "Player%d: Color", (i+1)); // Print the color and the players name to the text buffer
LCD_DisplayStringXY(TG_START_X,
TG_START_Y + TG_START_FONT_OFFSET_Y + i* (TG_START_BLOCK_HEIGHT + TG_START_BLOCK_SPACING),
buf );
}
//Draw "Game Speed:" String
LCD_DisplayStringXY(TG_START_X,
TG_START_Y + PLAYER_COUNT*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING) + TG_START_FONT_OFFSET_Y,
"Game Speed:");
//Draw bounding box of gamespeed bar
LCD_DrawRect( TG_START_COL2_X,
TG_START_Y + PLAYER_COUNT*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING),
TG_START_COL2_WIDTH,
TG_START_BLOCK_HEIGHT,
GUI_COLOR_WHITE);
//Draw Info texts
int i=0;
while(texts[i]!=NULL) {
LCD_DisplayStringXY(TG_START_X,
TG_START_Y + (PLAYER_COUNT + 1)*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING) + TG_START_FONT_OFFSET_Y + i * TG_START_FONT_HEIGHT,
texts[i]);
i++;
}
uint8_t switches_old = 0;
uint16_t adc_old =0;
uint16_t player1_color=0, player2_color=0,game_speed=0;
bool first = true;
while(!io_button_has_edge(BTN_START)) { //As long as nobody presses the start button
uint8_t switches = read_switches(); //read current switches value
uint16_t adc = read_adc(); //read current adc value
if(adc>ADC_MAX) { //adc value exeeds maximum
adc = ADC_MAX; //set to maximum
}
if(switches!=switches_old || first) { //switch values changed or we are in the first loop
game_get_color(switches,&player1_color,&player2_color); //calculate colors from switch value
//Draw player1 color
LCD_DrawRectF( TG_START_COL2_X,
TG_START_Y,
TG_START_COL2_WIDTH,
TG_START_BLOCK_HEIGHT,
player1_color);
//Draw player 2 color
LCD_DrawRectF( TG_START_COL2_X,
TG_START_Y + TG_START_BLOCK_HEIGHT + TG_START_BLOCK_SPACING,
TG_START_COL2_WIDTH,
TG_START_BLOCK_HEIGHT,
player2_color);
switches_old = switches; //save switch state, to detect edge
}
if(abs( (int16_t) adc - adc_old ) > ADC_TOLERANCE || first) { //adc value changed quite a bit or we are in the first loop
uint8_t bar_width = (TG_START_COL2_WIDTH -2)*adc/ADC_MAX;
game_speed = (ADC_MAX-adc)*(SPEED_SLOW-SPEED_FAST)/ADC_MAX + SPEED_FAST;
//Draw background part of bar
LCD_DrawRectF(TG_START_COL2_X + 1 + bar_width,
TG_START_Y + PLAYER_COUNT*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING) + 1,
TG_START_COL2_WIDTH - 2 - bar_width,
TG_START_BLOCK_HEIGHT-2,
GUI_COLOR_BLACK);
//Draw foreground part of bar
LCD_DrawRectF( TG_START_COL2_X + 1,
TG_START_Y + PLAYER_COUNT*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING) + 1,
bar_width,
TG_START_BLOCK_HEIGHT - 2,
GUI_COLOR_BLUE);
//For debug purposes
/*static char buf[16]; // Text buffer
LCD_SetTextColor(GUI_COLOR_WHITE);
sprintf(buf, "speed: %02d", game_speed);
LCD_DisplayStringXY(TG_START_COL2_X + TG_START_COL2_WIDTH + TG_START_FONT_OFFSET_Y,
TG_START_Y + TG_START_FONT_OFFSET_Y + PLAYER_COUNT*(TG_START_BLOCK_HEIGHT+TG_START_BLOCK_SPACING) + 1,
buf );*/
adc_old = adc; //save old adc value, to detect change
}
first = false; // we're no longer in the first loop
}
// Setup the two players
//Init player 1
player_init(&(game->player[0]), // Fill object of player 1
BTN_PLAYER_1_LEFT, // Left-button of player1
BTN_PLAYER_1_RIGHT, // Right-button of player 1
(point_t) { //Start point of player 1
.x=(TG_FIELD_START_OFFSET + TG_FIELD_LEFT), // x start coordinate
.y=(((TFT_HEIGHT - TG_FIELD_TOP - TG_FIELD_BOTTOM) / 2) + TG_FIELD_TOP) // y start coordinate
},
player1_color, // color of player 1
right); // initial moving direction of player 2
//Init player 2
player_init(&(game->player[1]),
BTN_PLAYER_2_LEFT,
BTN_PLAYER_2_RIGHT,
(point_t) {
.x=(TFT_WIDTH - 1 - TG_FIELD_RIGHT - TG_FIELD_START_OFFSET), // x start coordinate
.y=(((TFT_HEIGHT - TG_FIELD_TOP - TG_FIELD_BOTTOM) / 2) + TG_FIELD_TOP) // y start coordinate
},
player2_color,
left);
game->ticks_per_pixel = game_speed;
game->state = running; // Switch the game state to running
game->time = 0; // Reset the game time
LCD_Clear(GUI_COLOR_BLACK); // Clear the background
// Draw the game boundary
LCD_DrawRect(TG_FIELD_LEFT, // left top x
TG_FIELD_TOP, // left top y
(TFT_WIDTH - TG_FIELD_LEFT - TG_FIELD_RIGHT - 1), // right bottom x
(TFT_HEIGHT - TG_FIELD_TOP - TG_FIELD_BOTTOM - 1), // right bottom y
GUI_COLOR_WHITE); // Color of the boundary
LCD_SetTextColor(GUI_COLOR_WHITE); // Reset color to white
LCD_DisplayStringXY(TG_HEADER_TIME_X, TG_HEADER_TIME_Y, "Time: 0:00"); // Draw the zero-time
for(int i = 0; i < PLAYER_COUNT; i++){ // For each player print its name and its state
static char buf[16]; // Text buffer
LCD_SetTextColor(game->player[i].color); // Set the text color according to the players color
sprintf(buf, "Player%d: alive", (i+1)); // Print the state and the players name to the text buffer
LCD_DisplayStringXY(TG_HEADER_PLAYER_X+i*TG_HEADER_PLAYER_WIDTH, TG_HEADER_PLAYER_Y, buf); // Print everything
}
return true;
}
bool game_step_running(game_t* game, uint64_t delta_time)
{
uint16_t ticks;
uint16_t pixels = 0;
if(delta_time) {
ticks = game->ticks_leftover + delta_time; // Calculate the number of past ticks
pixels = ticks / game->ticks_per_pixel; // Calculate the number of pixels moved in the calculated amount of ticks
game->ticks_leftover = ticks % game->ticks_per_pixel; // Calculate the number of ticks which are left
game->ticks_sum_sec += delta_time; // Add the delta_time to the tick sum which is used to calculate the game time
uint16_t new_seconds = game->ticks_sum_sec / game->ticks_per_sec; // Calculate number of seconds from past ticks
game->time += new_seconds; // Add the new amount of seconds to the game time
game->ticks_sum_sec = game->ticks_sum_sec % game->ticks_per_sec; // Limit the tick sum used to calculate the amount of seconds
if(new_seconds > 0){ // Print the time if it got updated
static char buf[15]; // Textbufer
sprintf(buf, "Time: %d:%02d", (game->time / 60), (game->time % 60)); // Format time and paste it to the textbuffer
LCD_SetTextColor(GUI_COLOR_WHITE); // Set the text color to white
LCD_DisplayStringXY(TG_HEADER_TIME_X, TG_HEADER_TIME_Y, buf); // Print the time
}
}
bool all_players_dead = true; // Assume all players are dead ;-)
// For each player do ...
for(int i = 0; i < PLAYER_COUNT; i++) {
player_t* player = &(game->player[i]); // Copy an object of the current player
if(game_player_update(game, player, pixels)) { // Update player and execute if, when player state has changed
static char buf[15]; // Buffer to hold the text output
const char* state_text = "alive"; // Assume that the player is alive
if(player->state==dead) { // If the player is dead change the text
state_text="dead";
}
sprintf(buf, "Player%d: %s ", (i+1),state_text); // Format and paste the status to the buffer
LCD_SetTextColor(player->color); // Set the text color to the players color
LCD_DisplayStringXY(TG_HEADER_PLAYER_X+i*TG_HEADER_PLAYER_WIDTH, TG_HEADER_PLAYER_Y, buf); // Print the status
}
if(player->state!=dead) { // If the current player still lives not all players are dead ...
all_players_dead=false;
}
}
if(all_players_dead) { // End the game if all players are dead
game->state=ended; // Set the state to ended
return true;
} else {
return false;
}
}
bool game_step_ended(game_t* game) {
while(!io_button_has_edge(BTN_START)); // Wait for the start button to be pressed again
LCD_Clear(GUI_COLOR_BLACK); // Clear the background
game->state= prestart; // Set the state to prestart
return true;
}
bool game_step(game_t* game, uint64_t delta_time) { // Calculate the next game step
switch(game->state) {
case init:
return game_step_init(game);
case prestart:
return game_step_prestart(game);
case running:
return game_step_running(game,delta_time);
case ended:
return game_step_ended(game);
}
}