Skip to content

File main.cpp

File List > COD_ENG_OUT_arduino_code > src > main.cpp

Go to the documentation of this file

#include <ASL.hpp>
#include <Arduino.h>
#include <defines.hpp>
#include <logic.hpp>

// Global Variables, because they are used in the interupt routine or setup
// function.
volatile ASL::en_state en_current_state = ASL::display_setup_real_players;
// used in ISR (but not changed):
uint8_t u8_player_quantity;
uint8_t u8_computer_quantity;

// Use int here, as invalid values are -1
volatile int8_t i8_current_player_number;
volatile int8_t i8_current_token_number;

// classes
LOGIC::cla_session *obj_session;
ASL::cla_display obj_display(A, B, C, CLK, LAT, OE);

// Used for blinking
volatile bool bool_blink_flag = false;

// Function Prototypes
extern void Move_Token(int8_t _i8_current_player_number,
                       uint8_t _u8_remove_position, uint8_t _u8_add_position,
                       ASL::cla_display *_obj_display,
                       LOGIC::cla_session *_obj_session,
                       uint8_t _u8_player_quantity, uint8_t _u8_dice_value);

#if TIMING_DEBUG_INTERN != 0
volatile uint8_t u8_timing_debug_array[101];
volatile uint8_t u8_timing_debug_counter = 0;
#endif

void setup() {
  obj_display.Set_Colors(0, RED_BRIGHT, RED_DARK);
  obj_display.Set_Colors(1, BLUE_BRIGHT, BLUE_DARK);
  obj_display.Set_Colors(2, YELLOW_BRIGHT, YELLOW_DARK);
  obj_display.Set_Colors(3, GREEN_BRIGHT, GREEN_DARK);
  obj_display.Begin();
  ASL::Setup_Buttons();
  ASL::Setup_Dice();
#if DEBUG || TIMING_DEBUG
  // Set Port K to Output
  DDRK = 0xff;
#endif
#if TIMING_DEBUG_INTERN != 0
  // Set Timer 2 to prescaler 0
  TCCR2A = 0x00;
  TCCR2B = 0x00;
  TCCR2B |= (1 << CS20);
  Serial.begin(9600);
#endif
}

void loop() {
  // Variables used to check if the new position is occupied
  static uint8_t u8_occupying_player;
  static uint8_t u8_occupying_token;
  static bool bool_occupied_flag;

  // dice variables
  static uint8_t u8_dice_value;
  static uint8_t u8_dice_roll_counter;

  // player setup variables
  static LOGIC::mode en_computer_mode;

  static uint8_t u8_old_position;
  static uint8_t u8_new_position;
  switch (en_current_state) {
  // -----------------------------------------------------------------------------
  case ASL::display_setup_real_players:
    // ...........................................................................
    // .............................INITIALIZATION................................
    // ...........................................................................
    // This is the first state, so we will set some initial values here.
    u8_dice_value = INITIAL_DICE_VALUE;
    u8_dice_roll_counter = INITIAL_DICE_ROLL_COUNTER;
    en_computer_mode = INITIAL_COMPUTER_MODE;
    u8_player_quantity = INITIAL_PLAYER_QUANTITY;
    u8_computer_quantity = INITIAL_COMPUTER_QUANTITY;
    i8_current_player_number = INITIAL_CURRENT_PLAYER_NUMBER;
    i8_current_token_number = INITIAL_CURRENT_TOKEN_NUMBER;
    bool_blink_flag = INITIAL_BOOL_BLINK_FLAG;
    // ...........................................................................
    if (obj_display.Blink_Is_On()) {
      obj_display.Blink_Stop();
    }
    obj_display.Display_Restore();
    obj_display.Display_Players(u8_player_quantity);
    obj_display.Display_Char('R', 'E', 'A');
    en_current_state = ASL::setup_real_players;
    break;
  // -----------------------------------------------------------------------------
  case ASL::setup_real_players:
    // NOP
    break;
  // -----------------------------------------------------------------------------
  case ASL::modify_real_player_number:
    if (u8_player_quantity < 4) {
      u8_player_quantity++;
    } else {
      u8_player_quantity = 0;
    }
    obj_display.Display_Players(u8_player_quantity);
#if DEBUG
    // First 4 Bits are for the current state, two for the player quantity and
    // two for computer quantity
    PORTK = en_current_state | ((u8_player_quantity - 1) << 4) |
            (u8_computer_quantity << 6);
#endif
    en_current_state = ASL::setup_real_players;
    break;
  // -----------------------------------------------------------------------------
  case ASL::display_setup_computer_players:
    obj_display.Display_Players(u8_player_quantity);
    obj_display.Display_Char('C', 'O', 'M');
    en_current_state = ASL::setup_computer_players;
    break;
  // -----------------------------------------------------------------------------
  case ASL::setup_computer_players:
    // NOP
    break;
  // -----------------------------------------------------------------------------
  case ASL::modify_computer_player_number:
    if (u8_player_quantity < 4) {
      u8_player_quantity++;
      u8_computer_quantity++;
    } else {
      u8_player_quantity = u8_player_quantity - u8_computer_quantity;
      u8_computer_quantity = 0;
    }
    obj_display.Display_Players(u8_player_quantity);
#if DEBUG
    // First 4 Bits are for the current state, two for the player quantity and
    // two for computer quantity
    PORTK = en_current_state | ((u8_player_quantity - 1) << 4) |
            (u8_computer_quantity << 6);
#endif
    en_current_state = ASL::setup_computer_players;
    break;
  // -----------------------------------------------------------------------------
  case ASL::display_setup_computer_player_mode:
    obj_display.Display_Char('S', 'T', 'U');
    en_current_state = ASL::setup_computer_player_mode;
    break;
  case ASL::setup_computer_player_mode:
    // display mode.
    break;
  // -----------------------------------------------------------------------------
  case ASL::modify_computer_player_mode:
    // modify the computer player mode
    switch (en_computer_mode) {
    case LOGIC::Student:
      en_computer_mode = LOGIC::Professor;
      obj_display.Display_Char('P', 'R', 'O');
      break;
    case LOGIC::Professor:
      en_computer_mode = LOGIC::Student;
      obj_display.Display_Char('S', 'T', 'U');
      break;
    }
    // Go back to setup state
    en_current_state = ASL::setup_computer_player_mode;
    break;
  // -----------------------------------------------------------------------------
  case ASL::init_game_logic:
    // remove txt from display
    obj_display.Display_Char();
    obj_session = new LOGIC::cla_session(
        u8_player_quantity, u8_computer_quantity, en_computer_mode);
#if DEBUG
    // First 4 Bits are for the current state, two for the player quantity and
    // two for computer quantity
    PORTK = en_current_state | ((u8_player_quantity - 1) << 4) |
            (u8_computer_quantity << 6);
#endif
    obj_display.Display_Current_Player(i8_current_player_number);
    if (u8_player_quantity != u8_computer_quantity) {
      // Only wait for dice roll, if there are real players
      en_current_state = ASL::wait_for_dice_roll;
    } else {
      // Set the player number to minus one: In next_player state, the next
      // player is chosen by adding one to the current player number, and we
      // want to start at player 0.
      i8_current_player_number = -1;
      en_current_state = ASL::next_player;
    }
    break;
  // -----------------------------------------------------------------------------
  case ASL::wait_for_dice_roll:
    // NOP
    break;
  // -----------------------------------------------------------------------------
  case ASL::roll_the_dice:
    u8_dice_value = ASL::Roll_Dice();
    u8_dice_roll_counter--;
    obj_display.Display_Dice(u8_dice_value, u8_dice_roll_counter,
                             i8_current_player_number);
    // You can roll the dice up to 3 times, when all your tokens are still in
    // the Starting Square and you did not get a 6.
    if ((obj_session->array_players[i8_current_player_number]
             ->Get_Player_Status() == LOGIC::Start) &&
        (u8_dice_roll_counter >= 1) && (u8_dice_value != 6)) {
      // one more chance for the same player
      en_current_state = ASL::wait_for_dice_roll;
    } else if ((obj_session->array_players[i8_current_player_number]
                    ->Get_Player_Status() == LOGIC::Start) &&
               u8_dice_roll_counter <= 0 && u8_dice_value != 6) {
      // If you did not get a 6 and dice roll counter is 0,
      // wait for next player to roll dice
      en_current_state = ASL::next_player;
    } else if (u8_dice_value == 6) {
      // If you got a 6, your token is moved out of the Starting Square
      en_current_state = ASL::validate_token;
      u8_dice_roll_counter = 1;
    } else {
      // If you got a number between 1 and 5, you can move a token
      en_current_state = ASL::validate_token;
    }
#if DEBUG
    PORTK = en_current_state | (i8_current_player_number << 4) |
            (i8_current_token_number << 6);
    ;
#endif
    break;
  // -----------------------------------------------------------------------------
  case ASL::wait_for_player_input:
    // NOP
    break;
  // -----------------------------------------------------------------------------
  case ASL::display_token: {
    // First, stop previous blinking, if it was blinking.
    obj_display.Blink_Stop();
    // Start blinking the token
    obj_display.Blink_Start(ASL::slow, BLINK_CYCLES_TOKEN, ASL::token,
                            i8_current_player_number, u8_occupying_player,
                            u8_new_position, bool_occupied_flag,
                            u8_old_position);

#if DEBUG
    PORTK = en_current_state | (i8_current_player_number << 4) |
            (i8_current_token_number << 6);
#endif
    en_current_state = ASL::wait_for_player_input;
  } break;
  // -----------------------------------------------------------------------------
  case ASL::validate_token: {
    if (i8_current_token_number < 3) {
      i8_current_token_number++;
    } else {
      i8_current_token_number = 0;
    }
#if DEBUG
    PORTK = en_current_state | (i8_current_player_number << 4) |
            (i8_current_token_number << 6);
#endif
    // Lets get the current player state, so we don't have to do it several
    // times in the else if statement.
    LOGIC::status en_player_state =
        obj_session->array_players[i8_current_player_number]
            ->Get_Player_Status();
    // check if a token must be moved from starting square
    if (obj_session->array_players[i8_current_player_number]
            ->Is_Start_Field_Occupied_By_Own_Token() != -1) {
      i8_current_token_number =
          obj_session->array_players[i8_current_player_number]
              ->Is_Start_Field_Occupied_By_Own_Token();
      en_current_state = ASL::move_token;
    } else if ((u8_dice_value == 6) &&
               ((en_player_state == LOGIC::Start) ||
                (en_player_state == LOGIC::Start_Track) ||
                (en_player_state == LOGIC::Start_Finished) ||
                (en_player_state == LOGIC::Start_Track_Finished))) {
      // if a 6 was rolled and there are still token in the starting square, the
      // player cannot choose a token to move, so lets look for a token in the
      // starting square
      i8_current_token_number = 0;
      while (obj_session->array_players[i8_current_player_number]
                 ->Get_Token_Position(i8_current_token_number) >= 5) {
        // increase token number as long as the token is not in the starting
        // square
        i8_current_token_number++;
      }
      en_current_state = ASL::move_token;
    }
    // if no specific token must be moved, check if any token can be moved
    else {
      // variable used to determine how many token can be moved
      uint8_t u8_token_counter = 0;
      // variable used to determine the next movable Token
      int8_t u8_next_movable_token = -1;
      // check how many tokens can be moved:
      for (uint8_t i = 0; i < 4; i++) {
        if (obj_session->array_players[i8_current_player_number]
                ->Calculate_Possible_Position(i, u8_dice_value) !=
            obj_session->array_players[i8_current_player_number]
                ->Get_Token_Position(i)) {
          u8_token_counter++;
          // determine the next movable token
          if (u8_next_movable_token == -1) {
            // if it is -1, no token was found yet, so use the current token
            u8_next_movable_token = i;
          } else if ((u8_next_movable_token < i8_current_token_number) &&
                     (i >= i8_current_token_number)) {
            // we want the smallest token number that is larger than the current
            // token number, if there is one.
            u8_next_movable_token = i;
          }
        }
      }
      // set current token number to the next movable token
      i8_current_token_number = u8_next_movable_token;
      // handle cases of tokens that can be moved.
      if (u8_token_counter == 0) {
        // If no token can be moved, next player is chosen
        en_current_state = ASL::next_player;
      } else if (u8_token_counter == 1) {
        // If only one token can be moved, it is chosen and moved.
        en_current_state = ASL::move_token;
      } else {
        // If more than one token can be moved, display it, so the Player can
        // Choose.
        en_current_state = ASL::display_token;
      }
    }
    // check if the new position is occupied and set the occupied flag
    // accordingly.
    bool_occupied_flag = obj_session->Is_Occupied(
        u8_occupying_player, u8_occupying_token,
        obj_session->array_players[i8_current_player_number]
            ->Calculate_Possible_Position(i8_current_token_number,
                                          u8_dice_value));
    // Get the old and new position of the token.
    u8_old_position = obj_session->array_players[i8_current_player_number]
                          ->Get_Token_Position(i8_current_token_number);
    u8_new_position = obj_session->array_players[i8_current_player_number]
                          ->Calculate_Possible_Position(i8_current_token_number,
                                                        u8_dice_value);
  } break;
  // -----------------------------------------------------------------------------
  case ASL::move_token: {
    obj_display.Blink_Stop();
    // move the token on the display
    if (u8_old_position >= 5) {
      Move_Token(i8_current_player_number, u8_old_position, u8_new_position,
                 &obj_display, obj_session, u8_player_quantity, u8_dice_value);
    } else {
      obj_display.Move_Token(i8_current_player_number, u8_old_position,
                             u8_new_position);
    }
    // move the token in the logic (must be done before setting the occupied
    // token, because the new position is set here.)
    obj_session->array_players[i8_current_player_number]->Move_Token(
        i8_current_token_number, u8_dice_value);
    // Move the occupying token on the display, if needed.
    if (bool_occupied_flag) {
      obj_display.Move_Token(
          u8_occupying_player, u8_new_position,
          obj_session->array_players[u8_occupying_player]->Get_Token_Position(
              u8_occupying_token));
      obj_display.Blink_Start(ASL::fast, BLINK_CYCLES_OCCUPIED_TOKEN,
                              ASL::token_thrown, i8_current_player_number,
                              u8_occupying_player, u8_new_position);
      bool_occupied_flag = false;
    }
    // determine the next state
    if (u8_dice_roll_counter >= 1) {
      en_current_state = ASL::wait_for_dice_roll;
    } else {
      en_current_state = ASL::next_player;
    }
  } break;
  // -----------------------------------------------------------------------------
  case ASL::next_player: {
    if (obj_session->array_players[i8_current_player_number]
            ->Get_Player_Status() == LOGIC::Finished) {
      // clear the right half of the display
      obj_display.Display_Clear_Right();
      // Start winner animation
      obj_display.Display_Progress(i8_current_player_number, 28);
      obj_display.Blink_Start(ASL::slow, BLINK_CYCLES_WINNER_ANIMATION,
                              ASL::winner_animation, i8_current_player_number,
                              BLINK_CYCLES_WINNER_ANIMATION);
      delete obj_session;
      en_current_state = ASL::game_finished;
      break;
    } else {
      if (i8_current_player_number < (u8_player_quantity - 1)) {
        i8_current_player_number++;
      } else {
        i8_current_player_number = 0;
      }
      // Calculate transfer parameter for display current player
      int8_t i8_tokens_at_home = 0;
      for (uint8_t i = 0; i < 4; i++) {
        if (obj_session->array_players[i8_current_player_number]
                ->Get_Token_Position(i) < 5) {
          i8_tokens_at_home |= (1 << i);
        }
      }
      // before the current player can be displayed, blinking animation (throw)
      // must be finished. Otherwise the wrong token might be displayed on the
      // position somebody was thrown from, because the blinking is restarted in
      // another mode (starting_square) and the Blink_Stop() method was never
      // called. This could lead to the wrong token being displayed, since the
      // position is alternating in the two colors. If the blinking gets
      // disrupted while the thrown token is displayed, the wrong token stays
      // displayed.
      while (obj_display.Blink_Is_On()) {
        // Wait for blinking animation to finish, so the thrown animation isn't
        // getting messed up by the next player's starting square blinking.
        // If no token was thrown, Blink_Is_On will immediatly return false and
        // the loop will never be executed.
        asm volatile("nop"); // Do nothing
      }
      obj_display.Display_Current_Player(i8_current_player_number,
                                         i8_tokens_at_home);
      // Display the progress bar:
      obj_display.Display_Progress(
          i8_current_player_number,
          obj_session->array_players[i8_current_player_number]
              ->Get_Player_Progress());
      //  Set the dice roll counter for the next player:
      if (obj_session->array_players[i8_current_player_number]
              ->Get_Player_Status() == LOGIC::Start) {
        // roll the dice 3 times
        u8_dice_roll_counter = DICE_ROLLS_AT_START;
      } else {
        // roll the dice 1 time (unless you get a 6)
        u8_dice_roll_counter = DICE_ROLLS_NORMAL;
      }
      en_current_state = ASL::wait_for_dice_roll;
      while (obj_display.Blink_Is_On()) {
        // Wait for blinking animation to finish, so the starting square isn't
        // getting messed up by the player leaving the starting square.
        asm volatile("nop"); // Do nothing
      }
      // ------ Auto-move if we have a computer player ---------
      if (obj_session->array_players[i8_current_player_number]->Is_Computer()) {
        while (u8_dice_roll_counter > 0) {
          // Computer code here
          u8_dice_value = ASL::Roll_Dice();
          if (u8_dice_value == 6) {
            u8_dice_roll_counter = DICE_ROLLS_AT_SIX;
          } else {
            u8_dice_roll_counter--;
          }
          obj_display.Display_Dice(u8_dice_value, u8_dice_roll_counter,
                                   i8_current_player_number);
          ASL::Delay_256(ANIMATION_SPEED_COMPUTER);
          i8_current_token_number =
              obj_session->array_players[i8_current_player_number]->Auto_Move(
                  u8_dice_value, bool_occupied_flag, u8_old_position);
          u8_new_position = obj_session->array_players[i8_current_player_number]
                                ->Get_Token_Position(i8_current_token_number);
          if (i8_current_token_number != -1) {
            // move the token on the display
            if (u8_old_position >= 5) {
              Move_Token(i8_current_player_number, u8_old_position,
                         u8_new_position, &obj_display, obj_session,
                         u8_player_quantity, u8_dice_value);
            } else {
              obj_display.Move_Token(i8_current_player_number, u8_old_position,
                                     u8_new_position);
            }
          }
          if (bool_occupied_flag) {
            u8_occupying_player = obj_session->u8_is_occupied_player_id;
            u8_occupying_token = obj_session->u8_is_occupied_token_number;
            obj_display.Move_Token(
                u8_occupying_player, u8_new_position,
                obj_session->array_players[u8_occupying_player]
                    ->Get_Token_Position(u8_occupying_token));
            obj_display.Blink_Start(ASL::fast, BLINK_CYCLES_OCCUPIED_TOKEN,
                                    ASL::token_thrown, i8_current_player_number,
                                    u8_occupying_player, u8_new_position);
            bool_occupied_flag = false;
          }
          if (u8_dice_roll_counter > 0) {
            ASL::Delay_256(ANIMATION_SPEED_COMPUTER);
          }
#if DEBUG
          PORTK = en_current_state | ((i8_current_player_number << 4) && 0x0f) |
                  ((i8_current_token_number << 6) && 0x0f);
#endif
          en_current_state = ASL::next_player;
        }
      }
    }
    // Reset the token number for the next player
    i8_current_token_number = 0;
  } break;
  // -----------------------------------------------------------------------------
  case ASL::game_finished:
    asm volatile("nop"); // Do nothing
    break;
  default:
    // An error occured, go back to setup.
    en_current_state = ASL::display_setup_real_players;
    break;
  }
  // ------------------------------Blinking--------------------------------------
  if (bool_blink_flag) {
    obj_display.Blink_Update(false);
    bool_blink_flag = false;
  }
#if TIMING_DEBUG_INTERN != 0
  if (u8_timing_debug_counter > TIMING_DEBUG_COUNT_TO) {
    obj_display.obj_matrix->dumpMatrix();
    Serial.println("--------------------");
    for (uint8_t i = 0; i < TIMING_DEBUG_COUNT_TO; i++) {
      Serial.print(u8_timing_debug_array[i]);
      Serial.print(" ");
    }
  }
#endif
}