#include <string.h>
#include "ES_Configure.h"
#include "ES_Framework.h"
#include "GameLogicFSM.h"
#include "AvatarService.h"
#include "ObstacleService.h"
#include "DisplayService.h"
#include "dbprintf.h"
#include "EventCheckers.h"
#include "PIC32_AD_Lib.h"
#include "PWM_PIC32.h"
#include "DM_Display.h"
/*----------------------------- Module Defines ----------------------------*/
#define HALF_SEC 500
#define FIVETY_MILLIS 50
#define TWO_SECS 2000
#define TEN_SECS 10000
#define CALIB_NUM_SAMPLES 50
#define TOKEN_MAX_PULSE 6125
#define TOKEN_MIN_PULSE 1400
#define AVATAR_SERVO_CH 1
#define TOKEN_SERVO_CH 2
#define AVATAR_MAX_PULSE 5000
#define AVATAR_MIN_PULSE 1250
#define OBSTACLE_NEUTRAL_PULSE 3750
#define ENV_MAX 200
#define MID_ADC 512
#define ALPHA_Q8 32
#define VOLUME_THRESHOLD 2
#define NO_INPUT_INTERVAL 20 // in seconds
/*---------------------------- Module Functions ---------------------------*/
/* prototypes for private functions for this machine.They should be functions
relevant to the behavior of this state machine
*/
/*---------------------------- Module Variables ---------------------------*/
static GameState_t CurrentState;
static uint32_t adcResults[3];
bool calib = false;
uint32_t calib_num = 0;
uint32_t calib_sum = 0;
uint32_t volumeOffset = 0;
int env = 0;
uint32_t lastVolume = 0;
bool tokenOut = false;
char welcomeMsg[] = "WELCOME! SCREAM TO JUMP... ";
int msgIdx = 0;
int numTokens = 0;
int numTokensDone = 0;
int lastTimeVolumeChange;
// global variables
int currentHit = 0;
bool gameStarted = false;
// with the introduction of Gen2, we need a module level Priority var as well
static uint8_t MyPriority;
/*------------------------------ Module Code ------------------------------*/
/****************************************************************************
Function
InitTemplateFSM
Parameters
uint8_t : the priorty of this service
Returns
bool, false if error in initialization, true otherwise
Description
Saves away the priority, sets up the initial transition and does any
other required initialization for this state machine
Notes
Author
J. Edward Carryer, 10/23/11, 18:55
****************************************************************************/
bool InitGameLogicFSM(uint8_t Priority)
{
ES_Event_t ThisEvent;
MyPriority = Priority;
// put us into the Initial PseudoState
CurrentState = GAME_INIT;
lastTimeVolumeChange = currentTimeLeft;
currentAvatarTick = AVATAR_MIN_PULSE;
currentObstacleTick = OBSTACLE_NEUTRAL_PULSE;
InitButtonState();
InitPotValue();
InitSensorValue();
// post the initial transition event
ThisEvent.EventType = ES_INIT;
if (ES_PostToService(MyPriority, ThisEvent) == true)
{
return true;
}
else
{
return false;
}
}
/****************************************************************************
Function
PostTemplateFSM
Parameters
EF_Event_t ThisEvent , the event to post to the queue
Returns
boolean False if the Enqueue operation failed, True otherwise
Description
Posts an event to this state machine's queue
Notes
Author
J. Edward Carryer, 10/23/11, 19:25
****************************************************************************/
bool PostGameLogicFSM(ES_Event_t ThisEvent)
{
return ES_PostToService(MyPriority, ThisEvent);
}
/****************************************************************************
Function
RunTemplateFSM
Parameters
ES_Event_t : the event to process
Returns
ES_Event_t, ES_NO_EVENT if no error ES_ERROR otherwise
Description
add your description here
Notes
uses nested switch/case to implement the machine.
Author
J. Edward Carryer, 01/15/12, 15:23
****************************************************************************/
ES_Event_t RunGameLogicFSM(ES_Event_t ThisEvent)
{
ES_Event_t ReturnEvent;
ReturnEvent.EventType = ES_NO_EVENT; // assume no errors
switch (CurrentState)
{
case GAME_INIT:
{
if (ThisEvent.EventType == ES_INIT)
{
DB_printf("GameLogic initialized.\n");
CurrentState = GAME_IDLE;
if (ES_Timer_OK == ES_Timer_InitTimer(GAME_TIMER1, HALF_SEC))
{
DB_printf("set timer\n");
}
else {
DB_printf("failed to set timer\n");
}
}
}
break;
case GAME_IDLE: // If current state is state one
{
switch (ThisEvent.EventType)
{
case ES_TIMEOUT:
{
if (ThisEvent.EventParam == GAME_TIMER1) {
if (msgIdx == strlen(welcomeMsg))
{
msgIdx = 0;
}
uint8_t ch = welcomeMsg[msgIdx];
ES_Event_t e = {
.EventType = ES_ADD_CHAR,
.EventParam = ch
};
PostDisplayService(e);
ES_Timer_InitTimer(GAME_TIMER1, HALF_SEC);
msgIdx++;
// DB_printf("Posting ES_ADD_CHAR (%c)\n", ch);
}
}
break;
case ES_START_BUTTON_PRESS:
{
DB_printf("GAME STARTED!\n");
msgIdx = 0;
ES_Timer_InitTimer(GAME_TIMER1, FIVETY_MILLIS);
CurrentState = GAME_PLAYING;
}
break;
// repeat cases as required for relevant events
default:
;
} // end switch on CurrentEvent
}
break;
case GAME_PLAYING: // If current state is state one
{
switch (ThisEvent.EventType)
{
case ES_TIMEOUT:
{
if (ThisEvent.EventParam == GAME_TIMER1) {
if (!calib)
{
ADC_MultiRead(adcResults);
int sample = (int)adcResults[0] - MID_ADC;
int amp = (sample < 0) ? -sample : sample;
calib_sum += amp;
calib_num++;
if (calib_num >= CALIB_NUM_SAMPLES)
{
calib = true;
gameStarted = true;
volumeOffset = calib_sum / CALIB_NUM_SAMPLES;
DB_printf("Offset value: %d\n", volumeOffset);
calib_sum = 0;
calib_num = 0;
ES_Event_t e;
e.EventType = ES_CALIB_DONE;
PostObstacleService(e);
PostDisplayService(e);
}
}
else
{
int rect_env = envelopeStep();
int diff = (rect_env > lastVolume) ? (rect_env - lastVolume) : (lastVolume - rect_env);
if (diff < 0 || (rect_env > VOLUME_THRESHOLD && diff > 0))
{
// DB_printf("!!! POSTING VOLUME_CHANGE EVENT !!!\n");
lastTimeVolumeChange = currentTimeLeft;
ES_Event_t e;
e.EventType = ES_VOLUME_CHANGE;
e.EventParam = rect_env;
PostAvatarService(e);
} else if (lastTimeVolumeChange - currentTimeLeft >= NO_INPUT_INTERVAL)
{
ES_Event_t e;
e.EventType = ES_NO_INPUT_TIMEOUT;
PostGameLogicFSM(e);
PostAvatarService(e);
PostObstacleService(e);
PostDisplayService(e);
}
lastVolume = rect_env;
}
ES_Timer_InitTimer(GAME_TIMER1, FIVETY_MILLIS);
}
}
break;
case ES_GAMEOVER_TIMEOUT:
case ES_NO_INPUT_TIMEOUT:
{
DB_printf("GAMEOVER!\n");
if (ThisEvent.EventType == ES_NO_INPUT_TIMEOUT)
{
numTokens = 0;
}
else if (currentHit == 0)
{
numTokens = 3;
}
else if (currentHit < 3)
{
numTokens = 2;
}
else
{
numTokens = 1;
}
CurrentState = GAME_GAMEOVER;
gameStarted = false;
if (ES_Timer_OK != ES_Timer_InitTimer(GAME_TIMER3, TEN_SECS)) // timer to restart
{
DB_printf("failed to set 10 sec timer\n");
}
if (ES_Timer_OK != ES_Timer_InitTimer(GAME_TIMER2, TWO_SECS)) // timer to dispense tokens
{
DB_printf("failed to set 2 sec timer\n");
}
}
break;
// repeat cases as required for relevant events
default:
break;
}
}
break;
case GAME_GAMEOVER: // If current state is state one
{
switch (ThisEvent.EventType)
{
case ES_TIMEOUT:
{
if (ThisEvent.EventParam == GAME_TIMER3) { // 10 secs
CurrentState = GAME_IDLE;
calib = false;
currentHit = 0;
numTokensDone = 0;
lastTimeVolumeChange = currentTimeLeft; // timer already reset
DM_ClearDisplayBuffer();
ES_Timer_InitTimer(GAME_TIMER1, HALF_SEC);
}
else if (ThisEvent.EventParam == GAME_TIMER2) { // 2 secs
DB_printf("token timer!\n");
tokenOut = !tokenOut;
if (tokenOut)
{
DB_printf("TOKEN MAX\n");
PWMOperate_SetPulseWidthOnChannel(TOKEN_MAX_PULSE, TOKEN_SERVO_CH);
}
else
{
DB_printf("TOKEN MIN\n");
PWMOperate_SetPulseWidthOnChannel(TOKEN_MIN_PULSE, TOKEN_SERVO_CH);
}
numTokensDone++;
if (numTokensDone < numTokens)
{
ES_Timer_InitTimer(GAME_TIMER2, TWO_SECS);
}
}
}
break;
default:
DB_printf("unknown event!!!\n");
break;
}
}
break;
default:
break;
} // end switch on Current State
return ReturnEvent;
}
/***************************************************************************
private functions
***************************************************************************/
int envelopeStep(void)
{
ADC_MultiRead(adcResults);
// DB_printf("ADC value: %d\n", adcResults[0]);
// DB_printf("pot value: %d\n", adcResults[1]);
int sample = (int)adcResults[0] - MID_ADC;
int amp = (sample < 0) ? -sample : sample;
// DB_printf("amplitude: %d\n", amp);
env += (ALPHA_Q8 * (amp - env)) >> 8;
int rect_env = env - volumeOffset;
if (rect_env < 0)
{
rect_env = 0;
}
// DB_printf("envelope value: %d\n", rect_env);
return rect_env;
}