Add vibro on move. Song on game end.
This commit is contained in:
parent
b8db448de7
commit
72b1d89078
@ -1,14 +1,20 @@
|
||||
# Rock Papper Scissors
|
||||
|
||||
## 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.
|
||||
|
||||
## Status
|
||||
|
||||
This is currently a work in progress...
|
||||
|
||||
- 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.
|
||||
- Vibro on button press (valid move).
|
||||
- "Song" when win/loss/draw.
|
||||
|
||||
Remaining work (for subghz version):
|
||||
- Tone when button pressed.
|
||||
|
||||
- Show games found & let user pick the game to join.
|
||||
- Log joined game into SD card.
|
||||
- Log game results into SD card.
|
||||
@ -21,20 +27,25 @@ Remaining work (for subghz version):
|
||||
- Add game ending animations.
|
||||
|
||||
Future ideas:
|
||||
|
||||
- Uses Princeton signals for second player.
|
||||
- Create stand-alone hardware badges that can play the game too.
|
||||
- Instead of subghz, use IR.
|
||||
- Instead of subghz, use GPIO.
|
||||
|
||||
## Installation Directions
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- 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"?
|
||||
|
||||
## 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.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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 "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!
|
||||
@ -58,16 +70,18 @@ These directions assume you are starting at the flipper desktop. If not, please
|
||||
|
||||
- Press the BACK button to exit.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
- 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
|
||||
```
|
||||
@ -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 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
|
||||
|
||||
- application.fam
|
||||
|
||||
- specifies the name of our application.
|
||||
- specifies the entry point for our application.
|
||||
- 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.
|
||||
|
||||
- rock_paper_scissors.png
|
||||
|
||||
- The icon for our application that shows up in the "Misc" folder.
|
||||
|
||||
- rock_paper_scissors.c
|
||||
|
@ -22,6 +22,9 @@ Down - Scissors
|
||||
#include <gui/icon.h>
|
||||
#include <locale/locale.h>
|
||||
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.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.
|
||||
@ -55,9 +58,17 @@ typedef enum {
|
||||
} DolphinImageIndex;
|
||||
|
||||
const Icon* images[] = {
|
||||
&I_Local_Looking, &I_Local_Ready,
|
||||
&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 };
|
||||
&I_Local_Looking,
|
||||
&I_Local_Ready,
|
||||
&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.
|
||||
// Some moves may be invalid depending on the current game state.
|
||||
@ -113,6 +124,7 @@ typedef enum {
|
||||
GameEventLocalMove,
|
||||
GameEventRemoteMove,
|
||||
GameEventSendMove,
|
||||
GameEventPlaySong,
|
||||
} GameEventType;
|
||||
|
||||
// An item in the event queue has both the type and its associated data.
|
||||
@ -144,7 +156,78 @@ typedef struct {
|
||||
SubGhzTxRxWorker* subghz_txrx;
|
||||
} 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.
|
||||
// Queue a GameEventDataDetected message.
|
||||
// @param ctx pointer to a GameContext
|
||||
@ -176,19 +259,19 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
|
||||
memset(message, 0x00, MESSAGE_MAX_LEN);
|
||||
size_t len = subghz_tx_rx_worker_read(game_context->subghz_txrx, message, MESSAGE_MAX_LEN);
|
||||
size_t game_name_len = strlen(RPS_GAME_NAME);
|
||||
if (len < (game_name_len + 2)) {
|
||||
if(len < (game_name_len + 2)) {
|
||||
FURI_LOG_D(TAG, "Message not long enough. >%s<", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// The message for a move (M) (like 'R' for Rock) using version (A) should be "RPS:" + "M" + "A" + game"###" + move"R" + ":" + "YourFlip" + "\r\n"
|
||||
if (strcmp(RPS_GAME_NAME, (const char*)message)) {
|
||||
if(strcmp(RPS_GAME_NAME, (const char*)message)) {
|
||||
FURI_LOG_D(TAG, "Got message >%s<", message);
|
||||
|
||||
// The purpose immediately follows the game name.
|
||||
GameRfPurpose purpose = message[game_name_len];
|
||||
// The version follows the purpose.
|
||||
uint8_t version = message[game_name_len+1];
|
||||
uint8_t version = message[game_name_len + 1];
|
||||
FURI_LOG_T(TAG, "Purpose is %c and version is %c", purpose, version);
|
||||
|
||||
// Null terminate buffer at the end of message so we can't overrun the buffer.
|
||||
@ -199,16 +282,26 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
|
||||
char senderName[9];
|
||||
char tmp;
|
||||
Move move = MoveUnknown;
|
||||
switch (purpose) {
|
||||
switch(purpose) {
|
||||
case GameRfPurposeMove:
|
||||
// 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;
|
||||
// IMPORTANT: The code processing the event needs to furi_string_free the senderName!
|
||||
FuriString* name = furi_string_alloc();
|
||||
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);
|
||||
} else {
|
||||
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:
|
||||
// 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!
|
||||
FuriString* name = furi_string_alloc();
|
||||
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);
|
||||
} else {
|
||||
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:
|
||||
// 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);
|
||||
|
||||
// IMPORTANT: The code processing the event needs to furi_string_free the senderName!
|
||||
FuriString* name = furi_string_alloc();
|
||||
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);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Failed to parse join message. >%s<", message);
|
||||
@ -246,7 +348,7 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
|
||||
break;
|
||||
|
||||
default:
|
||||
if (version <= MAJOR_VERSION) {
|
||||
if(version <= MAJOR_VERSION) {
|
||||
// The version is same or less than ours, so we should know about the message purpose.
|
||||
FURI_LOG_E(TAG, "Message purpose not handled for known version. >%s<", message);
|
||||
} else {
|
||||
@ -290,7 +392,7 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
switch (remotePlayer) {
|
||||
switch(remotePlayer) {
|
||||
case StateReady:
|
||||
canvas_draw_icon(canvas, 64, 0, images[DolphinRemoteReady]);
|
||||
break;
|
||||
@ -309,7 +411,7 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
|
||||
case StateTieRock:
|
||||
case StateWonRock:
|
||||
case StateLostRock:
|
||||
if (StateCount2 != localPlayer) {
|
||||
if(StateCount2 != localPlayer) {
|
||||
canvas_draw_icon(canvas, 64, 0, images[DolphinRemoteRock]);
|
||||
canvas_draw_str_aligned(canvas, 70, 55, AlignLeft, AlignTop, "Rock");
|
||||
} else {
|
||||
@ -321,7 +423,7 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
|
||||
case StateTiePaper:
|
||||
case StateWonPaper:
|
||||
case StateLostPaper:
|
||||
if (StateCount2 != localPlayer) {
|
||||
if(StateCount2 != localPlayer) {
|
||||
canvas_draw_icon(canvas, 64, 0, images[DolphinRemotePaper]);
|
||||
canvas_draw_str_aligned(canvas, 70, 55, AlignLeft, AlignTop, "Paper");
|
||||
} else {
|
||||
@ -333,7 +435,7 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
|
||||
case StateTieScissors:
|
||||
case StateWonScissors:
|
||||
case StateLostScissors:
|
||||
if (StateCount2 != localPlayer) {
|
||||
if(StateCount2 != localPlayer) {
|
||||
canvas_draw_icon(canvas, 64, 0, images[DolphinRemoteScissors]);
|
||||
canvas_draw_str_aligned(canvas, 70, 55, AlignLeft, AlignTop, "Scissors");
|
||||
} else {
|
||||
@ -345,11 +447,12 @@ static void rps_render_callback(Canvas* canvas, void* ctx) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (localPlayer) {
|
||||
switch(localPlayer) {
|
||||
case StateLookingForPlayer:
|
||||
canvas_draw_icon(canvas, 0, 0, images[DolphinLocalLooking]);
|
||||
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;
|
||||
|
||||
case StateReady:
|
||||
@ -467,14 +570,15 @@ static void rps_broadcast(GameContext* game_context, FuriString* buffer) {
|
||||
|
||||
// Make sure our message will fit into a packet; if not truncate it.
|
||||
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?
|
||||
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.
|
||||
message[MESSAGE_MAX_LEN-1] = 0;
|
||||
message[MESSAGE_MAX_LEN-2] = '\n';
|
||||
message[MESSAGE_MAX_LEN-3] = '\r';
|
||||
message[MESSAGE_MAX_LEN - 1] = 0;
|
||||
message[MESSAGE_MAX_LEN - 2] = '\n';
|
||||
message[MESSAGE_MAX_LEN - 3] = '\r';
|
||||
length = MESSAGE_MAX_LEN;
|
||||
}
|
||||
|
||||
@ -493,7 +597,15 @@ static void rps_broadcast_move(GameContext* game_context, Move 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"
|
||||
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);
|
||||
}
|
||||
|
||||
@ -505,7 +617,14 @@ static void rps_broadcast_beacon(GameContext* game_context) {
|
||||
FURI_LOG_I(TAG, "Sending beacon");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -518,7 +637,15 @@ static void rps_broadcast_join(GameContext* game_context, unsigned int gameNumbe
|
||||
FURI_LOG_I(TAG, "Joining game %d.", gameNumber);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -527,7 +654,7 @@ static void rps_broadcast_join(GameContext* game_context, unsigned int gameNumbe
|
||||
static uint32_t duration(uint32_t tick) {
|
||||
uint32_t current = furi_get_tick();
|
||||
// Every 55 days the tick could wrap.
|
||||
if (current < tick) {
|
||||
if(current < tick) {
|
||||
FURI_LOG_T(TAG, "tick count wrapped! current:%ld prev:%ld", current, tick);
|
||||
return current + (UINT32_MAX - tick);
|
||||
}
|
||||
@ -535,33 +662,6 @@ static uint32_t duration(uint32_t 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).
|
||||
// @param state GameState to check.
|
||||
// @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.
|
||||
// @returns true if game state is a rock, paper, scissors.
|
||||
static bool isFinalMove(GameState state) {
|
||||
return (StateRock == state) ||
|
||||
(StatePaper == state) ||
|
||||
(StateScissors == state);
|
||||
return (StateRock == state) || (StatePaper == state) || (StateScissors == state);
|
||||
}
|
||||
|
||||
static bool isError(GameState state) {
|
||||
return (StateError == state) ||
|
||||
(StateErrorLocalFast == state) ||
|
||||
(StateErrorRemoteFast == state) ||
|
||||
(StateErrorRemoteTimeout == state);
|
||||
return (StateError == state) || (StateErrorLocalFast == state) ||
|
||||
(StateErrorRemoteFast == state) || (StateErrorRemoteTimeout == state);
|
||||
}
|
||||
|
||||
// Temporary timings, since I don't have second Flipper & send commands via laptop.
|
||||
@ -598,7 +694,7 @@ static void rps_state_machine_update(GameContext* game_context) {
|
||||
FURI_LOG_I(TAG, "Validating game state. local:%c Remote:%c", d->localPlayer, d->remotePlayer);
|
||||
|
||||
// Did player leave after joining?
|
||||
if ((StateReady == d->remotePlayer) &&
|
||||
if((StateReady == d->remotePlayer) &&
|
||||
(duration(d->remoteMoveTick) > DURATION_NO_MOVE_DETECTED_ERROR)) {
|
||||
d->remotePlayer = StateLookingForPlayer;
|
||||
d->remoteMoveTick = furi_get_tick();
|
||||
@ -610,8 +706,7 @@ static void rps_state_machine_update(GameContext* game_context) {
|
||||
}
|
||||
|
||||
// TEMP - After Error, we reset back to Looking for player.
|
||||
if (isError(d->localPlayer) &&
|
||||
(duration(d->localMoveTick) > DURATION_SHOW_ERROR)) {
|
||||
if(isError(d->localPlayer) && (duration(d->localMoveTick) > DURATION_SHOW_ERROR)) {
|
||||
d->remotePlayer = StateLookingForPlayer;
|
||||
d->remoteMoveTick = furi_get_tick();
|
||||
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.
|
||||
if (isResult(d->localPlayer) &&
|
||||
(duration(d->localMoveTick) > DURATION_WIN_LOSS_TIE)) {
|
||||
if(isResult(d->localPlayer) && (duration(d->localMoveTick) > DURATION_WIN_LOSS_TIE)) {
|
||||
d->remotePlayer = StateReady;
|
||||
d->remoteMoveTick = furi_get_tick();
|
||||
d->localPlayer = StateReady;
|
||||
@ -633,40 +727,40 @@ static void rps_state_machine_update(GameContext* game_context) {
|
||||
}
|
||||
|
||||
// Check for winner.
|
||||
if (isFinalMove(d->localPlayer) && isFinalMove(d->remotePlayer) &&
|
||||
if(isFinalMove(d->localPlayer) && isFinalMove(d->remotePlayer) &&
|
||||
(duration(d->localMoveTick) > DURATION_SHOW_MOVES)) {
|
||||
d->localMoveTick = furi_get_tick();
|
||||
d->remoteMoveTick = furi_get_tick();
|
||||
if ((d->localPlayer == StateRock) && (d->remotePlayer == StateScissors)) {
|
||||
if((d->localPlayer == StateRock) && (d->remotePlayer == StateScissors)) {
|
||||
d->localPlayer = StateWonRock;
|
||||
d->remotePlayer = StateLostScissors;
|
||||
FURI_LOG_I(TAG, "Local won w/Rock.");
|
||||
} else if ((d->localPlayer == StateScissors) && (d->remotePlayer == StatePaper)) {
|
||||
} else if((d->localPlayer == StateScissors) && (d->remotePlayer == StatePaper)) {
|
||||
d->localPlayer = StateWonScissors;
|
||||
d->remotePlayer = StateLostPaper;
|
||||
FURI_LOG_I(TAG, "Local won w/Scissors.");
|
||||
} else if ((d->localPlayer == StatePaper) && (d->remotePlayer == StateRock)) {
|
||||
} else if((d->localPlayer == StatePaper) && (d->remotePlayer == StateRock)) {
|
||||
d->localPlayer = StateWonPaper;
|
||||
d->remotePlayer = StateLostRock;
|
||||
FURI_LOG_I(TAG, "Local won w/Paper.");
|
||||
} else if ((d->localPlayer == StateRock) && (d->remotePlayer == StatePaper)) {
|
||||
} else if((d->localPlayer == StateRock) && (d->remotePlayer == StatePaper)) {
|
||||
d->localPlayer = StateLostRock;
|
||||
d->remotePlayer = StateWonPaper;
|
||||
FURI_LOG_I(TAG, "Remote won w/Paper.");
|
||||
} else if ((d->localPlayer == StateScissors) && (d->remotePlayer == StateRock)) {
|
||||
} else if((d->localPlayer == StateScissors) && (d->remotePlayer == StateRock)) {
|
||||
d->localPlayer = StateLostScissors;
|
||||
d->remotePlayer = StateWonRock;
|
||||
FURI_LOG_I(TAG, "Remote won w/Rock.");
|
||||
} else if ((d->localPlayer == StatePaper) && (d->remotePlayer == StateScissors)) {
|
||||
} else if((d->localPlayer == StatePaper) && (d->remotePlayer == StateScissors)) {
|
||||
d->localPlayer = StateLostPaper;
|
||||
d->remotePlayer = StateWonScissors;
|
||||
FURI_LOG_I(TAG, "Remote won w/Scissors.");
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Tie game.");
|
||||
if (d->localPlayer == StateRock) {
|
||||
if(d->localPlayer == StateRock) {
|
||||
d->localPlayer = StateTieRock;
|
||||
d->remotePlayer = StateTieRock;
|
||||
} else if (d->localPlayer == StatePaper) {
|
||||
} else if(d->localPlayer == StatePaper) {
|
||||
d->localPlayer = StateTiePaper;
|
||||
d->remotePlayer = StateTiePaper;
|
||||
} else {
|
||||
@ -674,20 +768,24 @@ static void rps_state_machine_update(GameContext* game_context) {
|
||||
d->remotePlayer = StateTieScissors;
|
||||
}
|
||||
}
|
||||
|
||||
GameEvent event = {.type = GameEventPlaySong};
|
||||
furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the state machine to reflect that a remote user joined the game.
|
||||
// @param game_context pointer to a GameContext.
|
||||
static void rps_state_machine_remote_joined(GameContext* game_context) {
|
||||
if (StateLookingForPlayer == game_context->data->localPlayer) {
|
||||
if(StateLookingForPlayer == game_context->data->localPlayer) {
|
||||
FURI_LOG_I(TAG, "Remote player joined our game!");
|
||||
game_context->data->remotePlayer = StateReady;
|
||||
game_context->data->remoteMoveTick = furi_get_tick();
|
||||
game_context->data->localPlayer = StateReady;
|
||||
game_context->data->localMoveTick = furi_get_tick();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -700,58 +798,77 @@ static bool rps_state_machine_local_moved(GameContext* game_context, Move move)
|
||||
Move localMove = MoveUnknown;
|
||||
GameState localState = StateReady;
|
||||
|
||||
if (MoveCount == move && StateReady == game_context->data->localPlayer) {
|
||||
if(MoveCount == move && StateReady == game_context->data->localPlayer) {
|
||||
localMove = MoveCount1;
|
||||
localState = StateCount1;
|
||||
} else if (MoveCount == move && StateCount1 == game_context->data->localPlayer) {
|
||||
if ((StateCount1 == game_context->data->remotePlayer) ||
|
||||
} else if(MoveCount == move && StateCount1 == game_context->data->localPlayer) {
|
||||
if((StateCount1 == game_context->data->remotePlayer) ||
|
||||
(StateCount2 == game_context->data->remotePlayer)) {
|
||||
localMove = MoveCount2;
|
||||
localState = StateCount2;
|
||||
} else {
|
||||
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) {
|
||||
if (MoveRock == move) {
|
||||
if ((StateCount2 == game_context->data->remotePlayer) ||
|
||||
} else if(StateCount2 == game_context->data->localPlayer) {
|
||||
if(MoveRock == move) {
|
||||
if((StateCount2 == game_context->data->remotePlayer) ||
|
||||
isFinalMove(game_context->data->remotePlayer)) {
|
||||
localMove = MoveRock;
|
||||
localState = StateRock;
|
||||
} else {
|
||||
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) {
|
||||
if ((StateCount2 == game_context->data->remotePlayer) ||
|
||||
} else if(MovePaper == move) {
|
||||
if((StateCount2 == game_context->data->remotePlayer) ||
|
||||
isFinalMove(game_context->data->remotePlayer)) {
|
||||
localMove = MovePaper;
|
||||
localState = StatePaper;
|
||||
} else {
|
||||
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) {
|
||||
if ((StateCount2 == game_context->data->remotePlayer) ||
|
||||
} else if(MoveScissors == move) {
|
||||
if((StateCount2 == game_context->data->remotePlayer) ||
|
||||
isFinalMove(game_context->data->remotePlayer)) {
|
||||
localMove = MoveScissors;
|
||||
localState = StateScissors;
|
||||
} else {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
if (StateReady != localState) {
|
||||
if(StateReady != localState) {
|
||||
game_context->data->localPlayer = localState;
|
||||
game_context->data->localMoveTick = furi_get_tick();
|
||||
}
|
||||
@ -766,46 +883,55 @@ static bool rps_state_machine_remote_moved(GameContext* game_context, Move move)
|
||||
GameState remoteState = StateReady;
|
||||
FURI_LOG_I(TAG, "Remote move %c.", move);
|
||||
|
||||
if (MoveCount1 == move && StateReady == game_context->data->remotePlayer) {
|
||||
if(MoveCount1 == move && StateReady == game_context->data->remotePlayer) {
|
||||
remoteState = StateCount1;
|
||||
} else if (MoveCount2 == move && StateCount1 == game_context->data->remotePlayer) {
|
||||
if ((StateCount1 == game_context->data->localPlayer) ||
|
||||
} else if(MoveCount2 == move && StateCount1 == game_context->data->remotePlayer) {
|
||||
if((StateCount1 == game_context->data->localPlayer) ||
|
||||
(StateCount2 == game_context->data->localPlayer)) {
|
||||
remoteState = StateCount2;
|
||||
} else {
|
||||
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) {
|
||||
if ((StateCount2 == game_context->data->localPlayer) ||
|
||||
} else if(MoveRock == move && StateCount2 == game_context->data->remotePlayer) {
|
||||
if((StateCount2 == game_context->data->localPlayer) ||
|
||||
isFinalMove(game_context->data->localPlayer)) {
|
||||
remoteState = StateRock;
|
||||
} else {
|
||||
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) {
|
||||
if ((StateCount2 == game_context->data->localPlayer) ||
|
||||
} else if(MovePaper == move && StateCount2 == game_context->data->remotePlayer) {
|
||||
if((StateCount2 == game_context->data->localPlayer) ||
|
||||
isFinalMove(game_context->data->localPlayer)) {
|
||||
remoteState = StatePaper;
|
||||
} else {
|
||||
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) {
|
||||
if ((StateCount2 == game_context->data->localPlayer) ||
|
||||
} else if(MoveScissors == move && StateCount2 == game_context->data->remotePlayer) {
|
||||
if((StateCount2 == game_context->data->localPlayer) ||
|
||||
isFinalMove(game_context->data->localPlayer)) {
|
||||
remoteState = StateScissors;
|
||||
} else {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
if (StateReady != remoteState) {
|
||||
if(StateReady != remoteState) {
|
||||
game_context->data->remotePlayer = remoteState;
|
||||
game_context->data->remoteMoveTick = furi_get_tick();
|
||||
}
|
||||
@ -883,20 +1009,16 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
uint8_t beaconCounter = 0;
|
||||
bool processing = true;
|
||||
do {
|
||||
if (furi_message_queue_get(game_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
switch (event.type) {
|
||||
if(furi_message_queue_get(game_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
switch(event.type) {
|
||||
case GameEventTypeKey:
|
||||
if((event.input.type == InputTypeShort) &&
|
||||
(event.input.key == InputKeyBack)) {
|
||||
if((event.input.type == InputTypeShort) && (event.input.key == InputKeyBack)) {
|
||||
processing = false;
|
||||
} else if (event.input.type == InputTypeShort) {
|
||||
} else if(event.input.type == InputTypeShort) {
|
||||
unsigned int joinGameNumber;
|
||||
GameEvent newEvent = {
|
||||
.type = GameEventLocalMove,
|
||||
.tick = furi_get_tick(),
|
||||
.move = MoveUnknown
|
||||
};
|
||||
switch (event.input.key) {
|
||||
.type = GameEventLocalMove, .tick = furi_get_tick(), .move = MoveUnknown};
|
||||
switch(event.input.key) {
|
||||
case InputKeyOk:
|
||||
newEvent.move = MoveCount;
|
||||
break;
|
||||
@ -913,7 +1035,8 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
case InputKeyLeft:
|
||||
// Temporary: For now, we send "Join" when left button clicked.
|
||||
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_state_machine_remote_joined(game_context);
|
||||
furi_mutex_release(game_context->mutex);
|
||||
@ -927,17 +1050,20 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (newEvent.move != MoveUnknown) {
|
||||
if(newEvent.move != MoveUnknown) {
|
||||
furi_message_queue_put(game_context->queue, &newEvent, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GameEventPlaySong:
|
||||
play_song(game_context->data->localPlayer);
|
||||
break;
|
||||
case GameEventDataDetected:
|
||||
rps_receive_data(game_context, event.tick);
|
||||
break;
|
||||
case GameEventTypeTimer:
|
||||
if(furi_mutex_acquire(game_context->mutex, FuriWaitForever) == FuriStatusOk) {
|
||||
if (StateLookingForPlayer == game_context->data->localPlayer &&
|
||||
if(StateLookingForPlayer == game_context->data->localPlayer &&
|
||||
++beaconCounter >= BEACON_DURATION) {
|
||||
rps_broadcast_beacon(game_context);
|
||||
beaconCounter = 0;
|
||||
@ -953,10 +1079,11 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
break;
|
||||
case GameEventRemoteJoined:
|
||||
if(furi_mutex_acquire(game_context->mutex, FuriWaitForever) == FuriStatusOk) {
|
||||
if (event.gameNumber == game_context->data->gameNumber) {
|
||||
if(event.gameNumber == game_context->data->gameNumber) {
|
||||
rps_state_machine_remote_joined(game_context);
|
||||
} 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);
|
||||
} else {
|
||||
@ -985,7 +1112,7 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
}
|
||||
|
||||
// If message contains a sender name furi_string, free it.
|
||||
if (event.senderName) {
|
||||
if(event.senderName) {
|
||||
furi_string_free(event.senderName);
|
||||
}
|
||||
|
||||
@ -996,7 +1123,7 @@ int32_t rock_paper_scissors_app(void* p) {
|
||||
FURI_LOG_E(TAG, "Issue encountered reading from queue. Exiting application.");
|
||||
processing = false;
|
||||
}
|
||||
} while (processing);
|
||||
} while(processing);
|
||||
|
||||
// Free resources
|
||||
furi_timer_free(timer);
|
||||
|
Loading…
Reference in New Issue
Block a user