Merge branch 'main' into endless_runner_port
This commit is contained in:
commit
e5dfd4c93a
@ -521,7 +521,7 @@ bool movement_enable_tap_detection_if_available(void) {
|
|||||||
// ramp data rate up to 400 Hz and high performance mode
|
// ramp data rate up to 400 Hz and high performance mode
|
||||||
lis2dw_set_low_noise_mode(true);
|
lis2dw_set_low_noise_mode(true);
|
||||||
lis2dw_set_data_rate(LIS2DW_DATA_RATE_HP_400_HZ);
|
lis2dw_set_data_rate(LIS2DW_DATA_RATE_HP_400_HZ);
|
||||||
lis2dw_set_mode(LIS2DW_MODE_HIGH_PERFORMANCE);
|
lis2dw_set_mode(LIS2DW_MODE_LOW_POWER);
|
||||||
|
|
||||||
// Settling time (1 sample duration, i.e. 1/400Hz)
|
// Settling time (1 sample duration, i.e. 1/400Hz)
|
||||||
delay_ms(3);
|
delay_ms(3);
|
||||||
@ -610,7 +610,7 @@ void app_init(void) {
|
|||||||
// check if we are plugged into USB power.
|
// check if we are plugged into USB power.
|
||||||
HAL_GPIO_VBUS_DET_in();
|
HAL_GPIO_VBUS_DET_in();
|
||||||
HAL_GPIO_VBUS_DET_pulldown();
|
HAL_GPIO_VBUS_DET_pulldown();
|
||||||
delay_ms(10);
|
delay_ms(100);
|
||||||
if (HAL_GPIO_VBUS_DET_read()){
|
if (HAL_GPIO_VBUS_DET_read()){
|
||||||
/// if so, enable USB functionality.
|
/// if so, enable USB functionality.
|
||||||
_watch_enable_usb();
|
_watch_enable_usb();
|
||||||
|
|||||||
@ -73,5 +73,6 @@
|
|||||||
#include "wareki_face.h"
|
#include "wareki_face.h"
|
||||||
#include "deadline_face.h"
|
#include "deadline_face.h"
|
||||||
#include "wordle_face.h"
|
#include "wordle_face.h"
|
||||||
|
#include "blackjack_face.h"
|
||||||
#include "endless_runner_face.h"
|
#include "endless_runner_face.h"
|
||||||
// New includes go above this line.
|
// New includes go above this line.
|
||||||
|
|||||||
@ -48,5 +48,6 @@ SRCS += \
|
|||||||
./watch-faces/sensor/lis2dw_monitor_face.c \
|
./watch-faces/sensor/lis2dw_monitor_face.c \
|
||||||
./watch-faces/complication/wareki_face.c \
|
./watch-faces/complication/wareki_face.c \
|
||||||
./watch-faces/complication/deadline_face.c \
|
./watch-faces/complication/deadline_face.c \
|
||||||
|
./watch-faces/complication/blackjack_face.c \
|
||||||
./watch-faces/complication/endless_runner_face.c \
|
./watch-faces/complication/endless_runner_face.c \
|
||||||
# New watch faces go above this line.
|
# New watch faces go above this line.
|
||||||
|
|||||||
467
watch-faces/complication/blackjack_face.c
Executable file
467
watch-faces/complication/blackjack_face.c
Executable file
@ -0,0 +1,467 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 David Volovskiy
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Emulator only: need time() to seed the random number generator.
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "blackjack_face.h"
|
||||||
|
#include "watch_common_display.h"
|
||||||
|
|
||||||
|
#define ACE 14
|
||||||
|
#define KING 13
|
||||||
|
#define QUEEN 12
|
||||||
|
#define JACK 11
|
||||||
|
|
||||||
|
#define MIN_CARD_VALUE 2
|
||||||
|
#define MAX_CARD_VALUE ACE
|
||||||
|
#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)
|
||||||
|
#define CARD_SUIT_COUNT 4
|
||||||
|
#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)
|
||||||
|
|
||||||
|
#define BLACKJACK_MAX_HAND_SIZE 11 // 4*1 + 4*2 + 3*3 = 21; 11 cards total
|
||||||
|
#define MAX_PLAYER_CARDS_DISPLAY 4
|
||||||
|
#define BOARD_DISPLAY_START 4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t score;
|
||||||
|
uint8_t idx_hand;
|
||||||
|
int8_t high_aces_in_hand;
|
||||||
|
uint8_t hand[BLACKJACK_MAX_HAND_SIZE];
|
||||||
|
} hand_info_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BJ_TITLE_SCREEN,
|
||||||
|
BJ_PLAYING,
|
||||||
|
BJ_DEALER_PLAYING,
|
||||||
|
BJ_BUST,
|
||||||
|
BJ_RESULT,
|
||||||
|
BJ_WIN_RATIO,
|
||||||
|
} game_state_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
A, B, C, D, E, F, G
|
||||||
|
} segment_t;
|
||||||
|
|
||||||
|
static bool tap_turned_on = false;
|
||||||
|
static game_state_t game_state;
|
||||||
|
static uint8_t deck[DECK_SIZE] = {0};
|
||||||
|
static uint8_t current_card = 0;
|
||||||
|
static blackjack_face_state_t *g_state = NULL;
|
||||||
|
hand_info_t player;
|
||||||
|
hand_info_t dealer;
|
||||||
|
|
||||||
|
static uint8_t generate_random_number(uint8_t num_values) {
|
||||||
|
// Emulator: use rand. Hardware: use arc4random.
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
return rand() % num_values;
|
||||||
|
#else
|
||||||
|
return arc4random_uniform(num_values);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stack_deck(void) {
|
||||||
|
for (size_t i = 0; i < CARD_RANK_COUNT; i++) {
|
||||||
|
for (size_t j = 0; j < CARD_SUIT_COUNT; j++)
|
||||||
|
deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shuffle_deck(void) {
|
||||||
|
// Randomize shuffle with Fisher Yates
|
||||||
|
size_t i;
|
||||||
|
size_t j;
|
||||||
|
uint8_t tmp;
|
||||||
|
|
||||||
|
for (i = DECK_SIZE - 1; i > 0; i--) {
|
||||||
|
j = generate_random_number(0xFF) % (i + 1);
|
||||||
|
tmp = deck[j];
|
||||||
|
deck[j] = deck[i];
|
||||||
|
deck[i] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_deck(void) {
|
||||||
|
current_card = 0;
|
||||||
|
shuffle_deck();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t get_next_card(void) {
|
||||||
|
if (current_card >= DECK_SIZE)
|
||||||
|
reset_deck();
|
||||||
|
return deck[current_card++];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t get_card_value(uint8_t card) {
|
||||||
|
switch (card)
|
||||||
|
{
|
||||||
|
case ACE:
|
||||||
|
return 11;
|
||||||
|
case KING:
|
||||||
|
case QUEEN:
|
||||||
|
case JACK:
|
||||||
|
return 10;
|
||||||
|
default:
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void modify_score_from_aces(hand_info_t *hand_info) {
|
||||||
|
while (hand_info->score > 21 && hand_info->high_aces_in_hand > 0) {
|
||||||
|
hand_info->score -= 10;
|
||||||
|
hand_info->high_aces_in_hand--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_hands(void) {
|
||||||
|
memset(&player, 0, sizeof(player));
|
||||||
|
memset(&dealer, 0, sizeof(dealer));
|
||||||
|
reset_deck();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void give_card(hand_info_t *hand_info) {
|
||||||
|
uint8_t card = get_next_card();
|
||||||
|
if (card == ACE) hand_info->high_aces_in_hand++;
|
||||||
|
hand_info->hand[hand_info->idx_hand++] = card;
|
||||||
|
uint8_t card_value = get_card_value(card);
|
||||||
|
hand_info->score += card_value;
|
||||||
|
modify_score_from_aces(hand_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_segment_at_position(segment_t segment, uint8_t position) {
|
||||||
|
digit_mapping_t segmap;
|
||||||
|
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) {
|
||||||
|
segmap = Custom_LCD_Display_Mapping[position];
|
||||||
|
} else {
|
||||||
|
segmap = Classic_LCD_Display_Mapping[position];
|
||||||
|
}
|
||||||
|
const uint8_t com_pin = segmap.segment[segment].address.com;
|
||||||
|
const uint8_t seg = segmap.segment[segment].address.seg;
|
||||||
|
watch_set_pixel(com_pin, seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_card_at_position(uint8_t card, uint8_t display_position) {
|
||||||
|
switch (card) {
|
||||||
|
case KING:
|
||||||
|
watch_display_character(' ', display_position);
|
||||||
|
set_segment_at_position(A, display_position);
|
||||||
|
set_segment_at_position(D, display_position);
|
||||||
|
set_segment_at_position(G, display_position);
|
||||||
|
break;
|
||||||
|
case QUEEN:
|
||||||
|
watch_display_character(' ', display_position);
|
||||||
|
set_segment_at_position(A, display_position);
|
||||||
|
set_segment_at_position(D, display_position);
|
||||||
|
break;
|
||||||
|
case JACK:
|
||||||
|
watch_display_character('-', display_position);
|
||||||
|
break;
|
||||||
|
case ACE:
|
||||||
|
watch_display_character(watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM ? 'A' : 'a', display_position);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
watch_display_character('0', display_position);
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const char display_char = card + '0';
|
||||||
|
watch_display_character(display_char, display_position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_player_hand(void) {
|
||||||
|
uint8_t card;
|
||||||
|
if (player.idx_hand <= MAX_PLAYER_CARDS_DISPLAY) {
|
||||||
|
card = player.hand[player.idx_hand - 1];
|
||||||
|
display_card_at_position(card, BOARD_DISPLAY_START + player.idx_hand - 1);
|
||||||
|
} else {
|
||||||
|
for (uint8_t i=0; i<MAX_PLAYER_CARDS_DISPLAY; i++) {
|
||||||
|
card = player.hand[player.idx_hand - MAX_PLAYER_CARDS_DISPLAY + i];
|
||||||
|
display_card_at_position(card, BOARD_DISPLAY_START + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_dealer_hand(void) {
|
||||||
|
uint8_t card = dealer.hand[dealer.idx_hand - 1];
|
||||||
|
display_card_at_position(card, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_score(uint8_t score, watch_position_t pos) {
|
||||||
|
char buf[4];
|
||||||
|
sprintf(buf, "%2d", score);
|
||||||
|
watch_display_text(pos, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_to_game_scores(bool add_to_wins) {
|
||||||
|
g_state->games_played++;
|
||||||
|
if (g_state->games_played == 0) {
|
||||||
|
// Overflow
|
||||||
|
g_state->games_played = 1;
|
||||||
|
g_state->games_won = add_to_wins ? 1 : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (add_to_wins) {
|
||||||
|
g_state->games_won++;
|
||||||
|
if (g_state->games_won == 0) {
|
||||||
|
// Overflow
|
||||||
|
g_state->games_played = 1;
|
||||||
|
g_state->games_won = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_win(void) {
|
||||||
|
game_state = BJ_RESULT;
|
||||||
|
add_to_game_scores(true);
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WlN ", " WIN");
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
display_score(dealer.score, WATCH_POSITION_TOP_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_lose(void) {
|
||||||
|
game_state = BJ_RESULT;
|
||||||
|
add_to_game_scores(false);
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "LOSE", "lOSE");
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
display_score(dealer.score, WATCH_POSITION_TOP_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_tie(void) {
|
||||||
|
game_state = BJ_RESULT;
|
||||||
|
// Don't record ties to the win ratio
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "TlE ", " TIE");
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_bust(void) {
|
||||||
|
game_state = BJ_RESULT;
|
||||||
|
add_to_game_scores(false);
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST", "BUST");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_title(void) {
|
||||||
|
game_state = BJ_TITLE_SCREEN;
|
||||||
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "BLACK ", "21");
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, " JACK ", "BLaKJK");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_win_ratio(blackjack_face_state_t *state) {
|
||||||
|
char buf[7];
|
||||||
|
game_state = BJ_WIN_RATIO;
|
||||||
|
uint8_t win_ratio = 0;
|
||||||
|
if (state->games_played > 0) { // Avoid dividing by zero
|
||||||
|
win_ratio = (uint8_t)(100 * state->games_won) / state->games_played;
|
||||||
|
}
|
||||||
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "WINS ", "WR");
|
||||||
|
sprintf(buf, "%3dPct", win_ratio);
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void begin_playing(bool tap_control_on) {
|
||||||
|
watch_clear_display();
|
||||||
|
if (tap_control_on) {
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
}
|
||||||
|
game_state = BJ_PLAYING;
|
||||||
|
reset_hands();
|
||||||
|
// Give player their first 2 cards
|
||||||
|
give_card(&player);
|
||||||
|
display_player_hand();
|
||||||
|
give_card(&player);
|
||||||
|
display_player_hand();
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
give_card(&dealer);
|
||||||
|
display_dealer_hand();
|
||||||
|
display_score(dealer.score, WATCH_POSITION_TOP_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void perform_stand(void) {
|
||||||
|
game_state = BJ_DEALER_PLAYING;
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, "Stnd");
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void perform_hit(void) {
|
||||||
|
if (player.score == 21) {
|
||||||
|
perform_stand();
|
||||||
|
return; // Assume hitting on 21 is a mistake and stand
|
||||||
|
}
|
||||||
|
give_card(&player);
|
||||||
|
if (player.score > 21) {
|
||||||
|
game_state = BJ_BUST;
|
||||||
|
}
|
||||||
|
display_player_hand();
|
||||||
|
display_score(player.score, WATCH_POSITION_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dealer_performs_hits(void) {
|
||||||
|
give_card(&dealer);
|
||||||
|
display_dealer_hand();
|
||||||
|
if (dealer.score > 21) {
|
||||||
|
display_win();
|
||||||
|
} else if (dealer.score > player.score) {
|
||||||
|
display_lose();
|
||||||
|
} else {
|
||||||
|
display_dealer_hand();
|
||||||
|
display_score(dealer.score, WATCH_POSITION_TOP_RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void see_if_dealer_hits(void) {
|
||||||
|
if (dealer.score > 16) {
|
||||||
|
if (dealer.score > player.score) {
|
||||||
|
display_lose();
|
||||||
|
} else if (dealer.score < player.score) {
|
||||||
|
display_win();
|
||||||
|
} else {
|
||||||
|
display_tie();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dealer_performs_hits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_button_presses(bool tap_control_on, bool hit) {
|
||||||
|
switch (game_state)
|
||||||
|
{
|
||||||
|
case BJ_TITLE_SCREEN:
|
||||||
|
if (!tap_turned_on && tap_control_on) {
|
||||||
|
if (movement_enable_tap_detection_if_available()) tap_turned_on = true;
|
||||||
|
}
|
||||||
|
begin_playing(tap_control_on);
|
||||||
|
break;
|
||||||
|
case BJ_PLAYING:
|
||||||
|
if (hit) {
|
||||||
|
perform_hit();
|
||||||
|
} else {
|
||||||
|
perform_stand();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BJ_DEALER_PLAYING:
|
||||||
|
see_if_dealer_hits();
|
||||||
|
break;
|
||||||
|
case BJ_BUST:
|
||||||
|
display_bust();
|
||||||
|
break;
|
||||||
|
case BJ_RESULT:
|
||||||
|
case BJ_WIN_RATIO:
|
||||||
|
display_title();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void toggle_tap_control(blackjack_face_state_t *state) {
|
||||||
|
if (state->tap_control_on) {
|
||||||
|
movement_disable_tap_detection_if_available();
|
||||||
|
state->tap_control_on = false;
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
} else {
|
||||||
|
bool tap_could_enable = movement_enable_tap_detection_if_available();
|
||||||
|
if (tap_could_enable) {
|
||||||
|
state->tap_control_on = true;
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void blackjack_face_setup(uint8_t watch_face_index, void **context_ptr) {
|
||||||
|
(void) watch_face_index;
|
||||||
|
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(blackjack_face_state_t));
|
||||||
|
memset(*context_ptr, 0, sizeof(blackjack_face_state_t));
|
||||||
|
blackjack_face_state_t *state = (blackjack_face_state_t *)*context_ptr;
|
||||||
|
state->tap_control_on = false;
|
||||||
|
}
|
||||||
|
g_state = (blackjack_face_state_t *)*context_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void blackjack_face_activate(void *context) {
|
||||||
|
blackjack_face_state_t *state = (blackjack_face_state_t *) context;
|
||||||
|
(void) state;
|
||||||
|
display_title();
|
||||||
|
stack_deck();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool blackjack_face_loop(movement_event_t event, void *context) {
|
||||||
|
blackjack_face_state_t *state = (blackjack_face_state_t *) context;
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_ACTIVATE:
|
||||||
|
if (state->tap_control_on) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
break;
|
||||||
|
case EVENT_TICK:
|
||||||
|
if (game_state == BJ_DEALER_PLAYING) {
|
||||||
|
see_if_dealer_hits();
|
||||||
|
}
|
||||||
|
else if (game_state == BJ_BUST) {
|
||||||
|
display_bust();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
|
case EVENT_DOUBLE_TAP:
|
||||||
|
handle_button_presses(state->tap_control_on, false);
|
||||||
|
case EVENT_LIGHT_BUTTON_DOWN:
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
|
case EVENT_SINGLE_TAP:
|
||||||
|
handle_button_presses(state->tap_control_on, true);
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_LONG_PRESS:
|
||||||
|
if (game_state == BJ_TITLE_SCREEN) {
|
||||||
|
display_win_ratio(state);
|
||||||
|
} else {
|
||||||
|
movement_illuminate_led();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
|
if (game_state == BJ_TITLE_SCREEN) {
|
||||||
|
toggle_tap_control(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_TIMEOUT:
|
||||||
|
case EVENT_LOW_ENERGY_UPDATE:
|
||||||
|
if (tap_turned_on) {
|
||||||
|
movement_disable_tap_detection_if_available();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return movement_default_loop_handler(event);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void blackjack_face_resign(void *context) {
|
||||||
|
(void) context;
|
||||||
|
if (tap_turned_on) {
|
||||||
|
tap_turned_on = false;
|
||||||
|
movement_disable_tap_detection_if_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
91
watch-faces/complication/blackjack_face.h
Executable file
91
watch-faces/complication/blackjack_face.h
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Chris Ellis
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BLACKJACK_FACE_H_
|
||||||
|
#define BLACKJACK_FACE_H_
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Blackjack face
|
||||||
|
* ======================
|
||||||
|
*
|
||||||
|
* Simple blackjack game.
|
||||||
|
*
|
||||||
|
* Aces are 11 unless you'd but, and if so, they become 1.
|
||||||
|
* King, Queen, and jack are all 10 points.
|
||||||
|
* Dealer deals to themselves until they get at least 17.
|
||||||
|
* The game plays with one shuffled deck that gets reshuffled with every game.
|
||||||
|
*
|
||||||
|
* Press either ALARM or LIGHT to begin playing.
|
||||||
|
* Your score is in the Seconds position.
|
||||||
|
* The dealer's score is in the Top-Right position.
|
||||||
|
* The dealer's last-shown card is in the Top-Left position.
|
||||||
|
* Your cards are in the Bottom row. From left to right, they are oldest to newest. Up to four cards will be dislayed.
|
||||||
|
*
|
||||||
|
* To hit, press the ALARM button.
|
||||||
|
* To stand, press the LIGHT button.
|
||||||
|
* If you're at 21, you cannoy hit, since we just assume it's a mispress on the button.
|
||||||
|
*
|
||||||
|
* Once you stand, the dealer will deal out to themselves once per second (or immidietly when you press the LIGHT or ALARM buttons).
|
||||||
|
* The game results are:
|
||||||
|
* WIN: You have a higher score than the dealer, but no more than 21. Or the dealer's score is over 21.
|
||||||
|
* LOSE: Your score is lower than the dealer's.
|
||||||
|
* BUST: Your score is above 21.
|
||||||
|
* TIE: Your score matches the dealer's final score
|
||||||
|
*
|
||||||
|
* On a watch that has the accelerometer, long-pressing the ALARM button will turn on the ability to play by tapping.
|
||||||
|
* The SIGNAL indicator will display when tapping is enabled.
|
||||||
|
* Tapping once will behave like the ALARM button and hit.
|
||||||
|
* Tapping twice behave like the LIGHT button and stand.
|
||||||
|
*
|
||||||
|
* | Cards | |
|
||||||
|
* |---------|--------------------------|
|
||||||
|
* | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|
|
||||||
|
* | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|a|
|
||||||
|
* If you're using a custom display, Ace will display as 'A', not 'a'
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool tap_control_on;
|
||||||
|
uint16_t games_played;
|
||||||
|
uint16_t games_won;
|
||||||
|
} blackjack_face_state_t;
|
||||||
|
|
||||||
|
void blackjack_face_setup(uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void blackjack_face_activate(void *context);
|
||||||
|
bool blackjack_face_loop(movement_event_t event, void *context);
|
||||||
|
void blackjack_face_resign(void *context);
|
||||||
|
|
||||||
|
#define blackjack_face ((const watch_face_t){ \
|
||||||
|
blackjack_face_setup, \
|
||||||
|
blackjack_face_activate, \
|
||||||
|
blackjack_face_loop, \
|
||||||
|
blackjack_face_resign, \
|
||||||
|
NULL, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // blackjack_FACE_H_
|
||||||
@ -161,19 +161,19 @@ static inline void _beep(beep_type_t beep_type)
|
|||||||
|
|
||||||
switch (beep_type) {
|
switch (beep_type) {
|
||||||
case BEEP_BUTTON:
|
case BEEP_BUTTON:
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
|
watch_buzzer_play_note_with_volume(BUZZER_NOTE_C7, 50, movement_button_volume());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BEEP_ENABLE:
|
case BEEP_ENABLE:
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_G7, 50);
|
watch_buzzer_play_note_with_volume(BUZZER_NOTE_G7, 50, movement_button_volume());
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
|
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_C8, 75);
|
watch_buzzer_play_note_with_volume(BUZZER_NOTE_C8, 50, movement_button_volume());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BEEP_DISABLE:
|
case BEEP_DISABLE:
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
|
watch_buzzer_play_note_with_volume(BUZZER_NOTE_C8, 50, movement_button_volume());
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
|
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_G7, 75);
|
watch_buzzer_play_note_with_volume(BUZZER_NOTE_G7, 50, movement_button_volume());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "simple_coin_flip_face.h"
|
#include "simple_coin_flip_face.h"
|
||||||
|
#include "delay.h"
|
||||||
|
|
||||||
void simple_coin_flip_face_setup(uint8_t watch_face_index, void ** context_ptr) {
|
void simple_coin_flip_face_setup(uint8_t watch_face_index, void ** context_ptr) {
|
||||||
(void) watch_face_index;
|
(void) watch_face_index;
|
||||||
@ -36,7 +37,7 @@ void simple_coin_flip_face_setup(uint8_t watch_face_index, void ** context_ptr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void simple_coin_flip_face_activate(void *context) {
|
void simple_coin_flip_face_activate(void *context) {
|
||||||
simple_coin_flip_face_state_t *state = (simple_coin_flip_face_state_t *)context;
|
(void) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t get_random(uint32_t max) {
|
static uint32_t get_random(uint32_t max) {
|
||||||
@ -48,7 +49,7 @@ static uint32_t get_random(uint32_t max) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_start_face() {
|
static void draw_start_face(void) {
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) {
|
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) {
|
||||||
watch_display_text(WATCH_POSITION_BOTTOM, " Flip");
|
watch_display_text(WATCH_POSITION_BOTTOM, " Flip");
|
||||||
@ -57,7 +58,7 @@ void draw_start_face() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_pixels(int pixels[3][4][2], int j_len) {
|
static void set_pixels(int pixels[3][4][2], int j_len) {
|
||||||
for(int loopruns = 0; loopruns<2; loopruns++) {
|
for(int loopruns = 0; loopruns<2; loopruns++) {
|
||||||
for(int i = 0; i<3; i++) {
|
for(int i = 0; i<3; i++) {
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
@ -69,7 +70,7 @@ void set_pixels(int pixels[3][4][2], int j_len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_animation() {
|
static void load_animation(void) {
|
||||||
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) {
|
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) {
|
||||||
int j_len = 2;
|
int j_len = 2;
|
||||||
int pixels[3][4][2] = {
|
int pixels[3][4][2] = {
|
||||||
@ -114,6 +115,7 @@ void load_animation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _blink_face_update_lcd(simple_coin_flip_face_state_t *state) {
|
static void _blink_face_update_lcd(simple_coin_flip_face_state_t *state) {
|
||||||
|
(void) state;
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
load_animation();
|
load_animation();
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
|
|||||||
@ -46,7 +46,7 @@ static void _handle_alarm_button(watch_date_time_t date_time, uint8_t current_pa
|
|||||||
current_offset = movement_get_current_timezone_offset_for_zone(movement_get_timezone_index());
|
current_offset = movement_get_current_timezone_offset_for_zone(movement_get_timezone_index());
|
||||||
return;
|
return;
|
||||||
case 0: // year
|
case 0: // year
|
||||||
date_time.unit.year = ((date_time.unit.year % 60) + 1);
|
date_time.unit.year = (date_time.unit.year + 1) % 60;
|
||||||
break;
|
break;
|
||||||
case 1: // month
|
case 1: // month
|
||||||
date_time.unit.month = (date_time.unit.month % 12) + 1;
|
date_time.unit.month = (date_time.unit.month % 12) + 1;
|
||||||
|
|||||||
@ -252,7 +252,7 @@ void watch_enable_display(void) {
|
|||||||
slcd_clear();
|
slcd_clear();
|
||||||
|
|
||||||
if (_installed_display == WATCH_LCD_TYPE_CUSTOM) {
|
if (_installed_display == WATCH_LCD_TYPE_CUSTOM) {
|
||||||
slcd_set_contrast(4);
|
slcd_set_contrast(0);
|
||||||
} else {
|
} else {
|
||||||
slcd_set_contrast(9);
|
slcd_set_contrast(9);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -205,7 +205,8 @@ uint32_t watch_utility_date_time_to_unix_time(watch_date_time_t date_time, int32
|
|||||||
watch_date_time_t watch_utility_date_time_from_unix_time(uint32_t timestamp, int32_t utc_offset) {
|
watch_date_time_t watch_utility_date_time_from_unix_time(uint32_t timestamp, int32_t utc_offset) {
|
||||||
watch_date_time_t retval;
|
watch_date_time_t retval;
|
||||||
retval.reg = 0;
|
retval.reg = 0;
|
||||||
int32_t days, secs;
|
uint32_t secs;
|
||||||
|
int32_t days;
|
||||||
int32_t remdays, remsecs, remyears;
|
int32_t remdays, remsecs, remyears;
|
||||||
int32_t qc_cycles, c_cycles, q_cycles;
|
int32_t qc_cycles, c_cycles, q_cycles;
|
||||||
int32_t years, months;
|
int32_t years, months;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user