Add vibro on move. Song on game end.

This commit is contained in:
Derek Jamison 2023-03-04 16:17:45 -05:00
parent b8db448de7
commit 72b1d89078
2 changed files with 555 additions and 413 deletions

View File

@ -1,14 +1,20 @@
# Rock Papper Scissors # Rock Papper Scissors
## Introduction ## Introduction
This is game of Rock, Paper, Scissors. In version 1, we use the subghz radio to find other players, communicate moves, and exchange contact information. This is game of Rock, Paper, Scissors. In version 1, we use the subghz radio to find other players, communicate moves, and exchange contact information.
## Status ## Status
This is currently a work in progress... This is currently a work in progress...
- Most of the game logic is complete (two flippers should be able to play against each other.) - Most of the game logic is complete (two flippers should be able to play against each other.)
- UI to show Flipper images instead of just debug text. - UI to show Flipper images instead of just debug text.
- Vibro on button press (valid move).
- "Song" when win/loss/draw.
Remaining work (for subghz version): Remaining work (for subghz version):
- Tone when button pressed.
- Show games found & let user pick the game to join. - Show games found & let user pick the game to join.
- Log joined game into SD card. - Log joined game into SD card.
- Log game results into SD card. - Log game results into SD card.
@ -21,20 +27,25 @@ Remaining work (for subghz version):
- Add game ending animations. - Add game ending animations.
Future ideas: Future ideas:
- Uses Princeton signals for second player. - Uses Princeton signals for second player.
- Create stand-alone hardware badges that can play the game too. - Create stand-alone hardware badges that can play the game too.
- Instead of subghz, use IR. - Instead of subghz, use IR.
- Instead of subghz, use GPIO. - Instead of subghz, use GPIO.
## Installation Directions ## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo. This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware. - Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "rock_paper_scissors" [folder](..) to the \applications\plugins\rock_paper_scissors folder in your firmware. - Copy the "rock_paper_scissors" [folder](..) to the \applications\plugins\rock_paper_scissors folder in your firmware.
- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware. - Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the rock_paper_scissors.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder. - NOTE: You can also extract the rock_paper_scissors.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
### Open question - Should we use "applications" or "applications_user"? ### Open question - Should we use "applications" or "applications_user"?
## Running the updated firmware ## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop. These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu. - Press the OK button on the flipper to pull up the main menu.
@ -47,6 +58,7 @@ These directions assume you are starting at the flipper desktop. If not, please
- On one of the flippers press Left arrow to join the game. - On one of the flippers press Left arrow to join the game.
- Once two players are joined: - Once two players are joined:
- Press "OK" to send "1". The other player should also press "OK" to send "1" back (at the same time!) - Press "OK" to send "1". The other player should also press "OK" to send "1" back (at the same time!)
- Press "OK" to send "2". The other player should also press "OK" to send "2" back (at the same time!) - Press "OK" to send "2". The other player should also press "OK" to send "2" back (at the same time!)
- Press "UP" to send a "Rock", or "RIGHT" to send a "Paper", or "DOWN" to send a "Scissors". The other player should send at same time! - Press "UP" to send a "Rock", or "RIGHT" to send a "Paper", or "DOWN" to send a "Scissors". The other player should send at same time!
@ -58,16 +70,18 @@ These directions assume you are starting at the flipper desktop. If not, please
- Press the BACK button to exit. - Press the BACK button to exit.
## HackRF One ## HackRF One
- If you do not have two Flipper Zero devices, you can use a HackRF One to record messages & broadcast those messages at a future date. I made a [YouTube](https://www.youtube.com/watch?v=S0sgcDQrVOc) video demo of how to record and broadcast messages. - If you do not have two Flipper Zero devices, you can use a HackRF One to record messages & broadcast those messages at a future date. I made a [YouTube](https://www.youtube.com/watch?v=S0sgcDQrVOc) video demo of how to record and broadcast messages.
- To record a message: (replace "flipper-chat.rf" with the file name you want to use, such as "select-rock.rf".) - To record a message: (replace "flipper-chat.rf" with the file name you want to use, such as "select-rock.rf".)
``` ```
sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000
``` ```
- To broadcast a message: (replace "flipper-chat.rf" with the saved file name.) - To broadcast a message: (replace "flipper-chat.rf" with the saved file name.)
``` ```
sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 -x 47 sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 -x 47
``` ```
@ -76,10 +90,10 @@ sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 -x 47
- Use the Flipper Zero to send a messasge that I record, then I play back that message at a later time when I want to pretend the other Flipper Zero is sending a message. You can use the chat app in https://lab.flipper.net/cli, like shown in the video to send a specific packet (or you can use your own code to create the packet.) - Use the Flipper Zero to send a messasge that I record, then I play back that message at a later time when I want to pretend the other Flipper Zero is sending a message. You can use the chat app in https://lab.flipper.net/cli, like shown in the video to send a specific packet (or you can use your own code to create the packet.)
- Use the HackRF One to record the message from my Fliiper Zero. Then later I use the chat app in https://lab.flipper.net/cli, to see what the message was (or I replay the message to see how my application would respond.) - Use the HackRF One to record the message from my Fliiper Zero. Then later I use the chat app in https://lab.flipper.net/cli, to see what the message was (or I replay the message to see how my application would respond.)
## Details about the project files ## Details about the project files
- application.fam - application.fam
- specifies the name of our application. - specifies the name of our application.
- specifies the entry point for our application. - specifies the entry point for our application.
- specifies we use the GUI. - specifies we use the GUI.
@ -87,6 +101,7 @@ sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 -x 47
- specifies our application can be found in the "Misc" category. - specifies our application can be found in the "Misc" category.
- rock_paper_scissors.png - rock_paper_scissors.png
- The icon for our application that shows up in the "Misc" folder. - The icon for our application that shows up in the "Misc" folder.
- rock_paper_scissors.c - rock_paper_scissors.c

View File

@ -22,6 +22,9 @@ Down - Scissors
#include <gui/icon.h> #include <gui/icon.h>
#include <locale/locale.h> #include <locale/locale.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <lib/subghz/subghz_tx_rx_worker.h> #include <lib/subghz/subghz_tx_rx_worker.h>
// This is sent at the beginning of all RF messages. NOTE: It must end with the ':' character. // This is sent at the beginning of all RF messages. NOTE: It must end with the ':' character.
@ -55,9 +58,17 @@ typedef enum {
} DolphinImageIndex; } DolphinImageIndex;
const Icon* images[] = { const Icon* images[] = {
&I_Local_Looking, &I_Local_Ready, &I_Local_Looking,
&I_Local_Count, &I_Local_Rock, &I_Local_Paper, &I_Local_Scissors, &I_Local_Ready,
&I_Remote_Ready, &I_Remote_Count, &I_Remote_Rock, &I_Remote_Paper, &I_Remote_Scissors }; &I_Local_Count,
&I_Local_Rock,
&I_Local_Paper,
&I_Local_Scissors,
&I_Remote_Ready,
&I_Remote_Count,
&I_Remote_Rock,
&I_Remote_Paper,
&I_Remote_Scissors};
// The various moves a player can make. // The various moves a player can make.
// Some moves may be invalid depending on the current game state. // Some moves may be invalid depending on the current game state.
@ -113,6 +124,7 @@ typedef enum {
GameEventLocalMove, GameEventLocalMove,
GameEventRemoteMove, GameEventRemoteMove,
GameEventSendMove, GameEventSendMove,
GameEventPlaySong,
} GameEventType; } GameEventType;
// An item in the event queue has both the type and its associated data. // An item in the event queue has both the type and its associated data.
@ -144,7 +156,78 @@ typedef struct {
SubGhzTxRxWorker* subghz_txrx; SubGhzTxRxWorker* subghz_txrx;
} GameContext; } GameContext;
// // Checks if game state is winner.
// @param state GameState to check.
// @returns true if game state is a winner.
static bool isWin(GameState state) {
return (StateWonPaper == state) || (StateWonRock == state) || (StateWonScissors == state);
}
// Checks if game state is lost.
// @param state GameState to check.
// @returns true if game state is a loss.
static bool isLoss(GameState state) {
return (StateLostPaper == state) || (StateLostRock == state) || (StateLostScissors == state);
}
// Checks if game state is tie.
// @param state GameState to check.
// @returns true if game state is a tie.
static bool isTie(GameState state) {
return (StateTiePaper == state) || (StateTieRock == state) || (StateTieScissors == state);
}
// When user makes a move, we briefly pulse the vibro motor.
static void single_vibro() {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro);
furi_record_close(RECORD_NOTIFICATION);
}
// Plays a note. You must acquire the speaker before invoking.
// @frequency the frequency of the note in Hz.
// @volume the volume of the note from 0.0 to 1.0
// @durationPlay the duration of the note in ms.
// @durationPause the duration after the note to be silent.
static void
play_note(float frequency, float volume, uint32_t durationPlay, uint32_t durationPause) {
furi_hal_speaker_start(frequency, volume);
uint32_t n = furi_get_tick();
while(furi_get_tick() < n + durationPlay) {
furi_thread_yield();
}
furi_hal_speaker_stop();
n = furi_get_tick();
while(furi_get_tick() < n + durationPause) {
furi_thread_yield();
}
}
// Play a song
static void play_song(GameState state) {
if(furi_hal_speaker_acquire(1000)) {
const float volume = 1.0f;
const uint32_t playQtr = 500;
const uint32_t delayQtr = 100;
if(isWin(state)) {
play_note(523.25f, volume, playQtr, delayQtr);
play_note(659.25f, volume, playQtr, delayQtr);
play_note(783.99f, volume, playQtr, delayQtr);
} else if(isLoss(state)) {
play_note(783.99f, volume, playQtr * 2, delayQtr);
play_note(523.25f, volume, playQtr, delayQtr);
} else if(isTie(state)) {
play_note(783.99f, volume, playQtr, delayQtr);
play_note(523.25f, volume, playQtr, delayQtr);
play_note(783.99f, volume, playQtr, delayQtr);
}
furi_hal_speaker_stop();
furi_hal_speaker_release();
}
}
// We register this callback to get invoked whenever new subghz data is received. // We register this callback to get invoked whenever new subghz data is received.
// Queue a GameEventDataDetected message. // Queue a GameEventDataDetected message.
// @param ctx pointer to a GameContext // @param ctx pointer to a GameContext
@ -202,13 +285,23 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
switch(purpose) { switch(purpose) {
case GameRfPurposeMove: case GameRfPurposeMove:
// We expect this mesage to the game number, move and sender name. // We expect this mesage to the game number, move and sender name.
if (sscanf((const char*)message+game_name_len+2, "%03u%c:%8s", &gameNumber, &tmp, senderName) == 3) { if(sscanf(
(const char*)message + game_name_len + 2,
"%03u%c:%8s",
&gameNumber,
&tmp,
senderName) == 3) {
move = (Move)tmp; move = (Move)tmp;
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, senderName); furi_string_set(name, senderName);
GameEvent event = {.type = GameEventRemoteMove, .move = move, .tick = tick, .senderName = name, .gameNumber = gameNumber }; GameEvent event = {
.type = GameEventRemoteMove,
.move = move,
.tick = tick,
.senderName = name,
.gameNumber = gameNumber};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else { } else {
FURI_LOG_W(TAG, "Failed to parse move message. >%s<", message); FURI_LOG_W(TAG, "Failed to parse move message. >%s<", message);
@ -217,12 +310,15 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
case GameRfPurposeBeacon: case GameRfPurposeBeacon:
// We expect this mesage to the game number, move and sender name. // We expect this mesage to the game number, move and sender name.
if (sscanf((const char*)message+game_name_len+2, "%03u:%8s", &gameNumber, senderName) == 2) { if(sscanf(
(const char*)message + game_name_len + 2, "%03u:%8s", &gameNumber, senderName) ==
2) {
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, senderName); furi_string_set(name, senderName);
GameEvent event = {.type = GameEventRemoteBeacon, .senderName = name, .gameNumber = gameNumber }; GameEvent event = {
.type = GameEventRemoteBeacon, .senderName = name, .gameNumber = gameNumber};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else { } else {
FURI_LOG_W(TAG, "Failed to parse beacon message. >%s<", message); FURI_LOG_W(TAG, "Failed to parse beacon message. >%s<", message);
@ -231,14 +327,20 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
case GameRfPurposeJoin: case GameRfPurposeJoin:
// We expect this mesage to the game number, move and sender name. // We expect this mesage to the game number, move and sender name.
if (sscanf((const char*)message+game_name_len+2, "%03u%s :%8s", &gameNumber, randomInfo, senderName) == 3) { if(sscanf(
(const char*)message + game_name_len + 2,
"%03u%s :%8s",
&gameNumber,
randomInfo,
senderName) == 3) {
FURI_LOG_T(TAG, "Join had randomInfo of >%s<", randomInfo); FURI_LOG_T(TAG, "Join had randomInfo of >%s<", randomInfo);
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, senderName); furi_string_set(name, senderName);
GameEvent event = {.type = GameEventRemoteJoined, .senderName = name, .gameNumber = gameNumber }; GameEvent event = {
.type = GameEventRemoteJoined, .senderName = name, .gameNumber = gameNumber};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else { } else {
FURI_LOG_W(TAG, "Failed to parse join message. >%s<", message); FURI_LOG_W(TAG, "Failed to parse join message. >%s<", message);
@ -349,7 +451,8 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
case StateLookingForPlayer: case StateLookingForPlayer:
canvas_draw_icon(canvas, 0, 0, images[DolphinLocalLooking]); canvas_draw_icon(canvas, 0, 0, images[DolphinLocalLooking]);
furi_string_printf(data->buffer, "Waiting for player, game %03d.", data->gameNumber); furi_string_printf(data->buffer, "Waiting for player, game %03d.", data->gameNumber);
canvas_draw_str_aligned(canvas, 0, 55, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer)); canvas_draw_str_aligned(
canvas, 0, 55, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
break; break;
case StateReady: case StateReady:
@ -469,7 +572,8 @@ static void rps_broadcast(GameContext* game_context, FuriString* buffer) {
size_t length = strlen((char*)message); size_t length = strlen((char*)message);
if(length > MESSAGE_MAX_LEN) { if(length > MESSAGE_MAX_LEN) {
// SECURITY REVIEW - Is it okay to log, or do we need to truncate first? // SECURITY REVIEW - Is it okay to log, or do we need to truncate first?
FURI_LOG_E(TAG, "Outgoing message bigger than %d bytes! >%s<", MESSAGE_MAX_LEN, (char*)message); FURI_LOG_E(
TAG, "Outgoing message bigger than %d bytes! >%s<", MESSAGE_MAX_LEN, (char*)message);
// Add \r\n(null) to the end of the 0-indexed string. // Add \r\n(null) to the end of the 0-indexed string.
message[MESSAGE_MAX_LEN - 1] = 0; message[MESSAGE_MAX_LEN - 1] = 0;
@ -493,7 +597,15 @@ static void rps_broadcast_move(GameContext* game_context, Move moveToSend) {
FURI_LOG_I(TAG, "Sending move %c", moveToSend); FURI_LOG_I(TAG, "Sending move %c", moveToSend);
// The message for game 42 with a move with value Rock should look like... "RPS:MA042R:YourFlip\r\n" // The message for game 42 with a move with value Rock should look like... "RPS:MA042R:YourFlip\r\n"
furi_string_printf(data->buffer, "%s%c%c%03u%c:%s\r\n", RPS_GAME_NAME, GameRfPurposeMove, MAJOR_VERSION, data->gameNumber, moveToSend, furi_hal_version_get_name_ptr()); furi_string_printf(
data->buffer,
"%s%c%c%03u%c:%s\r\n",
RPS_GAME_NAME,
GameRfPurposeMove,
MAJOR_VERSION,
data->gameNumber,
moveToSend,
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
@ -505,7 +617,14 @@ static void rps_broadcast_beacon(GameContext* game_context) {
FURI_LOG_I(TAG, "Sending beacon"); FURI_LOG_I(TAG, "Sending beacon");
// The message for game 42 should look like... "RPS:BA042:YourFlip\r\n" // The message for game 42 should look like... "RPS:BA042:YourFlip\r\n"
furi_string_printf(data->buffer, "%s%c%c%03u:%s\r\n", RPS_GAME_NAME, GameRfPurposeBeacon, MAJOR_VERSION, data->gameNumber, furi_hal_version_get_name_ptr()); furi_string_printf(
data->buffer,
"%s%c%c%03u:%s\r\n",
RPS_GAME_NAME,
GameRfPurposeBeacon,
MAJOR_VERSION,
data->gameNumber,
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
@ -518,7 +637,15 @@ static void rps_broadcast_join(GameContext* game_context, unsigned int gameNumbe
FURI_LOG_I(TAG, "Joining game %d.", gameNumber); FURI_LOG_I(TAG, "Joining game %d.", gameNumber);
// The message for game 42 should look like... "RPS:JA042NYourNameHere :YourFlip\r\n" // The message for game 42 should look like... "RPS:JA042NYourNameHere :YourFlip\r\n"
furi_string_printf(data->buffer, "%s%c%c%03u%s :%s\r\n", RPS_GAME_NAME, GameRfPurposeJoin, MAJOR_VERSION, data->gameNumber, CONTACT_INFO, furi_hal_version_get_name_ptr()); furi_string_printf(
data->buffer,
"%s%c%c%03u%s :%s\r\n",
RPS_GAME_NAME,
GameRfPurposeJoin,
MAJOR_VERSION,
data->gameNumber,
CONTACT_INFO,
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
@ -535,33 +662,6 @@ static uint32_t duration(uint32_t tick) {
return current - tick; return current - tick;
} }
// Checks if game state is winner.
// @param state GameState to check.
// @returns true if game state is a winner.
static bool isWin(GameState state) {
return (StateWonPaper == state) ||
(StateWonRock == state) ||
(StateWonScissors == state);
}
// Checks if game state is lost.
// @param state GameState to check.
// @returns true if game state is a loss.
static bool isLoss(GameState state) {
return (StateLostPaper == state) ||
(StateLostRock == state) ||
(StateLostScissors == state);
}
// Checks if game state is tie.
// @param state GameState to check.
// @returns true if game state is a tie.
static bool isTie(GameState state) {
return (StateTiePaper == state) ||
(StateTieRock == state) ||
(StateTieScissors == state);
}
// Checks if game state is result (win/loss/tie). // Checks if game state is result (win/loss/tie).
// @param state GameState to check. // @param state GameState to check.
// @returns true if game state is a win, loss or tie. // @returns true if game state is a win, loss or tie.
@ -573,16 +673,12 @@ static bool isResult(GameState state) {
// @param state GameState to check. // @param state GameState to check.
// @returns true if game state is a rock, paper, scissors. // @returns true if game state is a rock, paper, scissors.
static bool isFinalMove(GameState state) { static bool isFinalMove(GameState state) {
return (StateRock == state) || return (StateRock == state) || (StatePaper == state) || (StateScissors == state);
(StatePaper == state) ||
(StateScissors == state);
} }
static bool isError(GameState state) { static bool isError(GameState state) {
return (StateError == state) || return (StateError == state) || (StateErrorLocalFast == state) ||
(StateErrorLocalFast == state) || (StateErrorRemoteFast == state) || (StateErrorRemoteTimeout == state);
(StateErrorRemoteFast == state) ||
(StateErrorRemoteTimeout == state);
} }
// Temporary timings, since I don't have second Flipper & send commands via laptop. // Temporary timings, since I don't have second Flipper & send commands via laptop.
@ -610,8 +706,7 @@ static void rps_state_machine_update(GameContext* game_context) {
} }
// TEMP - After Error, we reset back to Looking for player. // TEMP - After Error, we reset back to Looking for player.
if (isError(d->localPlayer) && if(isError(d->localPlayer) && (duration(d->localMoveTick) > DURATION_SHOW_ERROR)) {
(duration(d->localMoveTick) > DURATION_SHOW_ERROR)) {
d->remotePlayer = StateLookingForPlayer; d->remotePlayer = StateLookingForPlayer;
d->remoteMoveTick = furi_get_tick(); d->remoteMoveTick = furi_get_tick();
d->localPlayer = StateLookingForPlayer; d->localPlayer = StateLookingForPlayer;
@ -621,8 +716,7 @@ static void rps_state_machine_update(GameContext* game_context) {
} }
// TEMP - After Win, Loss, Tie - we reset back to Ready. // TEMP - After Win, Loss, Tie - we reset back to Ready.
if (isResult(d->localPlayer) && if(isResult(d->localPlayer) && (duration(d->localMoveTick) > DURATION_WIN_LOSS_TIE)) {
(duration(d->localMoveTick) > DURATION_WIN_LOSS_TIE)) {
d->remotePlayer = StateReady; d->remotePlayer = StateReady;
d->remoteMoveTick = furi_get_tick(); d->remoteMoveTick = furi_get_tick();
d->localPlayer = StateReady; d->localPlayer = StateReady;
@ -674,6 +768,9 @@ static void rps_state_machine_update(GameContext* game_context) {
d->remotePlayer = StateTieScissors; d->remotePlayer = StateTieScissors;
} }
} }
GameEvent event = {.type = GameEventPlaySong};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} }
} }
@ -687,7 +784,8 @@ static void rps_state_machine_remote_joined(GameContext* game_context) {
game_context->data->localPlayer = StateReady; game_context->data->localPlayer = StateReady;
game_context->data->localMoveTick = furi_get_tick(); game_context->data->localMoveTick = furi_get_tick();
} else { } else {
FURI_LOG_I(TAG, "Remote requested join, but we are state %c!", game_context->data->localPlayer); FURI_LOG_I(
TAG, "Remote requested join, but we are state %c!", game_context->data->localPlayer);
} }
} }
@ -710,7 +808,8 @@ static bool rps_state_machine_local_moved(GameContext* game_context, Move move)
localState = StateCount2; localState = StateCount2;
} else { } else {
localState = StateErrorLocalFast; localState = StateErrorLocalFast;
FURI_LOG_I(TAG, "Local count sync error. remote is %c.", game_context->data->remotePlayer); FURI_LOG_I(
TAG, "Local count sync error. remote is %c.", game_context->data->remotePlayer);
} }
} else if(StateCount2 == game_context->data->localPlayer) { } else if(StateCount2 == game_context->data->localPlayer) {
if(MoveRock == move) { if(MoveRock == move) {
@ -720,7 +819,8 @@ static bool rps_state_machine_local_moved(GameContext* game_context, Move move)
localState = StateRock; localState = StateRock;
} else { } else {
localState = StateErrorLocalFast; localState = StateErrorLocalFast;
FURI_LOG_I(TAG, "Local rock sync error. remote is %c.", game_context->data->remotePlayer); FURI_LOG_I(
TAG, "Local rock sync error. remote is %c.", game_context->data->remotePlayer);
} }
} else if(MovePaper == move) { } else if(MovePaper == move) {
if((StateCount2 == game_context->data->remotePlayer) || if((StateCount2 == game_context->data->remotePlayer) ||
@ -729,7 +829,10 @@ static bool rps_state_machine_local_moved(GameContext* game_context, Move move)
localState = StatePaper; localState = StatePaper;
} else { } else {
localState = StateErrorLocalFast; localState = StateErrorLocalFast;
FURI_LOG_I(TAG, "Local paper sync error. remote is %c.", game_context->data->remotePlayer); FURI_LOG_I(
TAG,
"Local paper sync error. remote is %c.",
game_context->data->remotePlayer);
} }
} else if(MoveScissors == move) { } else if(MoveScissors == move) {
if((StateCount2 == game_context->data->remotePlayer) || if((StateCount2 == game_context->data->remotePlayer) ||
@ -738,16 +841,30 @@ static bool rps_state_machine_local_moved(GameContext* game_context, Move move)
localState = StateScissors; localState = StateScissors;
} else { } else {
localState = StateErrorLocalFast; localState = StateErrorLocalFast;
FURI_LOG_I(TAG, "Local scissors sync error. remote is %c.", game_context->data->remotePlayer); FURI_LOG_I(
TAG,
"Local scissors sync error. remote is %c.",
game_context->data->remotePlayer);
} }
} else { } else {
FURI_LOG_E(TAG, "Invalid Local move '%c' error. lState=%c. rState=%c.", move, game_context->data->localPlayer, game_context->data->remotePlayer); FURI_LOG_E(
TAG,
"Invalid Local move '%c' error. lState=%c. rState=%c.",
move,
game_context->data->localPlayer,
game_context->data->remotePlayer);
} }
} else { } else {
FURI_LOG_E(TAG, "Invalid Local move '%c' error. lState=%c. rState=%c.", move, game_context->data->localPlayer, game_context->data->remotePlayer); FURI_LOG_E(
TAG,
"Invalid Local move '%c' error. lState=%c. rState=%c.",
move,
game_context->data->localPlayer,
game_context->data->remotePlayer);
} }
if(MoveUnknown != localMove) { if(MoveUnknown != localMove) {
single_vibro();
rps_broadcast_move(game_context, localMove); rps_broadcast_move(game_context, localMove);
} }
@ -774,7 +891,8 @@ static bool rps_state_machine_remote_moved(GameContext* game_context, Move move)
remoteState = StateCount2; remoteState = StateCount2;
} else { } else {
remoteState = StateErrorRemoteFast; remoteState = StateErrorRemoteFast;
FURI_LOG_I(TAG, "Remote count sync error. local is %c.", game_context->data->localPlayer); FURI_LOG_I(
TAG, "Remote count sync error. local is %c.", game_context->data->localPlayer);
} }
} else if(MoveRock == move && StateCount2 == game_context->data->remotePlayer) { } else if(MoveRock == move && StateCount2 == game_context->data->remotePlayer) {
if((StateCount2 == game_context->data->localPlayer) || if((StateCount2 == game_context->data->localPlayer) ||
@ -782,7 +900,8 @@ static bool rps_state_machine_remote_moved(GameContext* game_context, Move move)
remoteState = StateRock; remoteState = StateRock;
} else { } else {
remoteState = StateErrorRemoteFast; remoteState = StateErrorRemoteFast;
FURI_LOG_I(TAG, "Remote rock sync error. local is %c.", game_context->data->localPlayer); FURI_LOG_I(
TAG, "Remote rock sync error. local is %c.", game_context->data->localPlayer);
} }
} else if(MovePaper == move && StateCount2 == game_context->data->remotePlayer) { } else if(MovePaper == move && StateCount2 == game_context->data->remotePlayer) {
if((StateCount2 == game_context->data->localPlayer) || if((StateCount2 == game_context->data->localPlayer) ||
@ -790,7 +909,8 @@ static bool rps_state_machine_remote_moved(GameContext* game_context, Move move)
remoteState = StatePaper; remoteState = StatePaper;
} else { } else {
remoteState = StateErrorRemoteFast; remoteState = StateErrorRemoteFast;
FURI_LOG_I(TAG, "Remote paper sync error. local is %c.", game_context->data->localPlayer); FURI_LOG_I(
TAG, "Remote paper sync error. local is %c.", game_context->data->localPlayer);
} }
} else if(MoveScissors == move && StateCount2 == game_context->data->remotePlayer) { } else if(MoveScissors == move && StateCount2 == game_context->data->remotePlayer) {
if((StateCount2 == game_context->data->localPlayer) || if((StateCount2 == game_context->data->localPlayer) ||
@ -798,10 +918,16 @@ static bool rps_state_machine_remote_moved(GameContext* game_context, Move move)
remoteState = StateScissors; remoteState = StateScissors;
} else { } else {
remoteState = StateErrorRemoteFast; remoteState = StateErrorRemoteFast;
FURI_LOG_I(TAG, "Remote scissors sync error. local is %c.", game_context->data->localPlayer); FURI_LOG_I(
TAG, "Remote scissors sync error. local is %c.", game_context->data->localPlayer);
} }
} else { } else {
FURI_LOG_E(TAG, "Remote move '%c' error. lState=%c. rState=%c.", move, game_context->data->localPlayer, game_context->data->remotePlayer); FURI_LOG_E(
TAG,
"Remote move '%c' error. lState=%c. rState=%c.",
move,
game_context->data->localPlayer,
game_context->data->remotePlayer);
remoteState = StateError; remoteState = StateError;
} }
@ -886,16 +1012,12 @@ int32_t rock_paper_scissors_app(void* p) {
if(furi_message_queue_get(game_context->queue, &event, FuriWaitForever) == FuriStatusOk) { if(furi_message_queue_get(game_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
switch(event.type) { switch(event.type) {
case GameEventTypeKey: case GameEventTypeKey:
if((event.input.type == InputTypeShort) && if((event.input.type == InputTypeShort) && (event.input.key == InputKeyBack)) {
(event.input.key == InputKeyBack)) {
processing = false; processing = false;
} else if(event.input.type == InputTypeShort) { } else if(event.input.type == InputTypeShort) {
unsigned int joinGameNumber; unsigned int joinGameNumber;
GameEvent newEvent = { GameEvent newEvent = {
.type = GameEventLocalMove, .type = GameEventLocalMove, .tick = furi_get_tick(), .move = MoveUnknown};
.tick = furi_get_tick(),
.move = MoveUnknown
};
switch(event.input.key) { switch(event.input.key) {
case InputKeyOk: case InputKeyOk:
newEvent.move = MoveCount; newEvent.move = MoveCount;
@ -913,7 +1035,8 @@ int32_t rock_paper_scissors_app(void* p) {
case InputKeyLeft: case InputKeyLeft:
// Temporary: For now, we send "Join" when left button clicked. // Temporary: For now, we send "Join" when left button clicked.
joinGameNumber = game_context->data->gameNumber; joinGameNumber = game_context->data->gameNumber;
if(furi_mutex_acquire(game_context->mutex, FuriWaitForever) == FuriStatusOk) { if(furi_mutex_acquire(game_context->mutex, FuriWaitForever) ==
FuriStatusOk) {
rps_broadcast_join(game_context, joinGameNumber); rps_broadcast_join(game_context, joinGameNumber);
rps_state_machine_remote_joined(game_context); rps_state_machine_remote_joined(game_context);
furi_mutex_release(game_context->mutex); furi_mutex_release(game_context->mutex);
@ -932,6 +1055,9 @@ int32_t rock_paper_scissors_app(void* p) {
} }
} }
break; break;
case GameEventPlaySong:
play_song(game_context->data->localPlayer);
break;
case GameEventDataDetected: case GameEventDataDetected:
rps_receive_data(game_context, event.tick); rps_receive_data(game_context, event.tick);
break; break;
@ -956,7 +1082,8 @@ int32_t rock_paper_scissors_app(void* p) {
if(event.gameNumber == game_context->data->gameNumber) { if(event.gameNumber == game_context->data->gameNumber) {
rps_state_machine_remote_joined(game_context); rps_state_machine_remote_joined(game_context);
} else { } else {
FURI_LOG_T(TAG, "Remote joining another Flipper on game %03u.", event.gameNumber); FURI_LOG_T(
TAG, "Remote joining another Flipper on game %03u.", event.gameNumber);
} }
furi_mutex_release(game_context->mutex); furi_mutex_release(game_context->mutex);
} else { } else {