Merge branch 'main' into simon-port
This commit is contained in:
commit
889f0f6ff0
@ -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,9 @@
|
|||||||
#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 "higher_lower_game_face.h"
|
||||||
|
#include "lander_face.h"
|
||||||
#include "simon_face.h"
|
#include "simon_face.h"
|
||||||
// New includes go above this line.
|
// New includes go above this line.
|
||||||
|
|||||||
@ -48,5 +48,9 @@ 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/higher_lower_game_face.c \
|
||||||
|
./watch-faces/complication/lander_face.c \
|
||||||
./watch-faces/complication/simon_face.c \
|
./watch-faces/complication/simon_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "endless_runner_face.h"
|
#include "endless_runner_face.h"
|
||||||
|
#include "delay.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JUMPING_FINAL_FRAME = 0,
|
JUMPING_FINAL_FRAME = 0,
|
||||||
@ -34,6 +35,7 @@ typedef enum {
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SCREEN_TITLE = 0,
|
SCREEN_TITLE = 0,
|
||||||
|
SCREEN_SCORE,
|
||||||
SCREEN_PLAYING,
|
SCREEN_PLAYING,
|
||||||
SCREEN_LOSE,
|
SCREEN_LOSE,
|
||||||
SCREEN_TIME,
|
SCREEN_TIME,
|
||||||
@ -77,14 +79,45 @@ typedef struct {
|
|||||||
uint8_t fuel;
|
uint8_t fuel;
|
||||||
} game_state_t;
|
} game_state_t;
|
||||||
|
|
||||||
|
// always-on, left, right, bottom, jump-top, jump-left, jump-right
|
||||||
|
int8_t classic_ball_arr_com[] = {1, 0, 1, 0, 2, 1, 2};
|
||||||
|
int8_t classic_ball_arr_seg[] = {20, 20, 21, 21, 20, 17, 21};
|
||||||
|
int8_t custom_ball_arr_com[] = {2, 1, 1, 0, 3, 3, 2};
|
||||||
|
int8_t custom_ball_arr_seg[] = {15, 15, 14, 15, 14, 15, 14};
|
||||||
|
|
||||||
|
// obstacle 0-11
|
||||||
|
int8_t classic_obstacle_arr_com[] = {0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1};
|
||||||
|
int8_t classic_obstacle_arr_seg[] = {18, 19, 20, 21, 22, 23, 0, 1, 2, 4, 5, 6};
|
||||||
|
int8_t custom_obstacle_arr_com[] = {1, 1, 1, 1, 1, 0, 1, 0, 3, 0, 0, 2};
|
||||||
|
int8_t custom_obstacle_arr_seg[] = {22, 16, 15, 14, 1, 2, 3, 4, 4, 5, 6, 7};
|
||||||
|
|
||||||
|
int8_t *ball_arr_com;
|
||||||
|
int8_t *ball_arr_seg;
|
||||||
|
int8_t *obstacle_arr_com;
|
||||||
|
int8_t *obstacle_arr_seg;
|
||||||
|
|
||||||
static game_state_t game_state;
|
static game_state_t game_state;
|
||||||
static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8;
|
static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8;
|
||||||
|
|
||||||
|
int8_t start_tune[] = {
|
||||||
|
BUZZER_NOTE_C5, 15,
|
||||||
|
BUZZER_NOTE_E5, 15,
|
||||||
|
BUZZER_NOTE_G5, 15,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
int8_t lose_tune[] = {
|
||||||
|
BUZZER_NOTE_D3, 10,
|
||||||
|
BUZZER_NOTE_C3SHARP_D3FLAT, 10,
|
||||||
|
BUZZER_NOTE_C3, 10,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
static void print_binary(uint32_t value, int bits) {
|
static void print_binary(uint32_t value, int bits) {
|
||||||
#if __EMSCRIPTEN__
|
#if __EMSCRIPTEN__
|
||||||
for (int i = bits - 1; i >= 0; i--) {
|
for (int i = bits - 1; i >= 0; i--) {
|
||||||
// Print each bit
|
// Print each bit
|
||||||
printf("%lu", (value >> i) & 1);
|
printf("%u", (value >> i) & 1);
|
||||||
// Optional: add a space every 4 bits for readability
|
// Optional: add a space every 4 bits for readability
|
||||||
if (i % 4 == 0 && i != 0) {
|
if (i % 4 == 0 && i != 0) {
|
||||||
printf(" ");
|
printf(" ");
|
||||||
@ -188,22 +221,22 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) {
|
|||||||
|
|
||||||
static void display_ball(bool jumping) {
|
static void display_ball(bool jumping) {
|
||||||
if (!jumping) {
|
if (!jumping) {
|
||||||
watch_set_pixel(0, 21);
|
watch_set_pixel(ball_arr_com[3], ball_arr_seg[3]);
|
||||||
watch_set_pixel(1, 21);
|
watch_set_pixel(ball_arr_com[2], ball_arr_seg[2]);
|
||||||
watch_set_pixel(0, 20);
|
watch_set_pixel(ball_arr_com[1], ball_arr_seg[1]);
|
||||||
watch_set_pixel(1, 20);
|
watch_set_pixel(ball_arr_com[0], ball_arr_seg[0]);
|
||||||
watch_clear_pixel(1, 17);
|
watch_clear_pixel(ball_arr_com[6], ball_arr_seg[6]);
|
||||||
watch_clear_pixel(2, 20);
|
watch_clear_pixel(ball_arr_com[5], ball_arr_seg[5]);
|
||||||
watch_clear_pixel(2, 21);
|
watch_clear_pixel(ball_arr_com[4], ball_arr_seg[4]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
watch_clear_pixel(0, 21);
|
watch_clear_pixel(ball_arr_com[3], ball_arr_seg[3]);
|
||||||
watch_clear_pixel(1, 21);
|
watch_clear_pixel(ball_arr_com[2], ball_arr_seg[2]);
|
||||||
watch_clear_pixel(0, 20);
|
watch_clear_pixel(ball_arr_com[1], ball_arr_seg[1]);
|
||||||
watch_set_pixel(1, 20);
|
watch_set_pixel(ball_arr_com[0], ball_arr_seg[0]);
|
||||||
watch_set_pixel(1, 17);
|
watch_set_pixel(ball_arr_com[6], ball_arr_seg[6]);
|
||||||
watch_set_pixel(2, 20);
|
watch_set_pixel(ball_arr_com[5], ball_arr_seg[5]);
|
||||||
watch_set_pixel(2, 21);
|
watch_set_pixel(ball_arr_com[4], ball_arr_seg[4]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,12 +245,12 @@ static void display_score(uint8_t score) {
|
|||||||
if (game_state.fuel_mode) {
|
if (game_state.fuel_mode) {
|
||||||
score %= (MAX_DISP_SCORE_FUEL + 1);
|
score %= (MAX_DISP_SCORE_FUEL + 1);
|
||||||
sprintf(buf, "%1d", score);
|
sprintf(buf, "%1d", score);
|
||||||
watch_display_string(buf, 0);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, buf);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
score %= (MAX_DISP_SCORE + 1);
|
score %= (MAX_DISP_SCORE + 1);
|
||||||
sprintf(buf, "%2d", score);
|
sprintf(buf, "%2d", score);
|
||||||
watch_display_string(buf, 2);
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,16 +267,16 @@ static void add_to_score(endless_runner_state_t *state) {
|
|||||||
static void display_fuel(uint8_t subsecond, uint8_t difficulty) {
|
static void display_fuel(uint8_t subsecond, uint8_t difficulty) {
|
||||||
char buf[4];
|
char buf[4];
|
||||||
if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % (FREQ/2) == 0) {
|
if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % (FREQ/2) == 0) {
|
||||||
watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled.
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); // Blink the 0 fuel to show it cannot be refilled.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sprintf(buf, "%2d", game_state.fuel);
|
sprintf(buf, "%2d", game_state.fuel);
|
||||||
watch_display_string(buf, 2);
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_and_reset_hi_score(endless_runner_state_t *state) {
|
static void check_and_reset_hi_score(endless_runner_state_t *state) {
|
||||||
// Resets the hi score at the beginning of each month.
|
// Resets the hi score at the beginning of each month.
|
||||||
watch_date_time_t date_time = watch_rtc_get_date_time();
|
watch_date_time_t date_time = movement_get_local_date_time();
|
||||||
if ((state -> year_last_hi_score != date_time.unit.year) ||
|
if ((state -> year_last_hi_score != date_time.unit.year) ||
|
||||||
(state -> month_last_hi_score != date_time.unit.month))
|
(state -> month_last_hi_score != date_time.unit.month))
|
||||||
{
|
{
|
||||||
@ -255,28 +288,15 @@ static void check_and_reset_hi_score(endless_runner_state_t *state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void display_difficulty(uint16_t difficulty) {
|
static void display_difficulty(uint16_t difficulty) {
|
||||||
switch (difficulty)
|
static const char *labels[] = {
|
||||||
{
|
[DIFF_BABY] = " b",
|
||||||
case DIFF_BABY:
|
[DIFF_EASY] = " E",
|
||||||
watch_display_string(" b", 2);
|
[DIFF_HARD] = " H",
|
||||||
break;
|
[DIFF_FUEL] = " F",
|
||||||
case DIFF_EASY:
|
[DIFF_FUEL_1] = "1F",
|
||||||
watch_display_string(" E", 2);
|
[DIFF_NORM] = " N"
|
||||||
break;
|
};
|
||||||
case DIFF_HARD:
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, labels[difficulty]);
|
||||||
watch_display_string(" H", 2);
|
|
||||||
break;
|
|
||||||
case DIFF_FUEL:
|
|
||||||
watch_display_string(" F", 2);
|
|
||||||
break;
|
|
||||||
case DIFF_FUEL_1:
|
|
||||||
watch_display_string("1F", 2);
|
|
||||||
break;
|
|
||||||
case DIFF_NORM:
|
|
||||||
default:
|
|
||||||
watch_display_string(" N", 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1;
|
game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,65 +309,93 @@ static void change_difficulty(endless_runner_state_t *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void toggle_sound(endless_runner_state_t *state) {
|
static void display_sound_indicator(bool soundOn) {
|
||||||
state -> soundOn = !state -> soundOn;
|
if (soundOn){
|
||||||
if (state -> soundOn){
|
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_C5, 30);
|
|
||||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void toggle_sound(endless_runner_state_t *state) {
|
||||||
|
state -> soundOn = !state -> soundOn;
|
||||||
|
display_sound_indicator(state -> soundOn);
|
||||||
|
if (state -> soundOn){
|
||||||
|
watch_buzzer_play_note(BUZZER_NOTE_C5, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enable_tap_control(endless_runner_state_t *state) {
|
||||||
|
if (!state->tap_control_on) {
|
||||||
|
movement_enable_tap_detection_if_available();
|
||||||
|
state->tap_control_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disable_tap_control(endless_runner_state_t *state) {
|
||||||
|
if (state->tap_control_on) {
|
||||||
|
movement_disable_tap_detection_if_available();
|
||||||
|
state->tap_control_on = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void display_title(endless_runner_state_t *state) {
|
static void display_title(endless_runner_state_t *state) {
|
||||||
|
game_state.curr_screen = SCREEN_TITLE;
|
||||||
|
watch_clear_colon();
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "ENdLS", "ER ");
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, "RUNNER");
|
||||||
|
display_sound_indicator(state -> soundOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_score_screen(endless_runner_state_t *state) {
|
||||||
uint16_t hi_score = state -> hi_score;
|
uint16_t hi_score = state -> hi_score;
|
||||||
uint8_t difficulty = state -> difficulty;
|
uint8_t difficulty = state -> difficulty;
|
||||||
bool sound_on = state -> soundOn;
|
bool sound_on = state -> soundOn;
|
||||||
game_state.curr_screen = SCREEN_TITLE;
|
|
||||||
memset(&game_state, 0, sizeof(game_state));
|
memset(&game_state, 0, sizeof(game_state));
|
||||||
|
game_state.curr_screen = SCREEN_SCORE;
|
||||||
game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay.
|
game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay.
|
||||||
if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second
|
if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second
|
||||||
watch_set_colon();
|
watch_set_colon();
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN ", "ER ");
|
||||||
if (hi_score > MAX_HI_SCORE) {
|
if (hi_score > MAX_HI_SCORE) {
|
||||||
watch_display_string("ER HS --", 0);
|
watch_display_text(WATCH_POSITION_BOTTOM, "HS --");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
char buf[14];
|
char buf[10];
|
||||||
sprintf(buf, "ER HS%4d", hi_score);
|
sprintf(buf, "HS%4d", hi_score);
|
||||||
watch_display_string(buf, 0);
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
}
|
}
|
||||||
display_difficulty(difficulty);
|
display_difficulty(difficulty);
|
||||||
|
display_sound_indicator(sound_on);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void display_time(watch_date_time_t date_time, bool clock_mode_24h) {
|
static void display_time(void) {
|
||||||
static watch_date_time_t previous_date_time;
|
static watch_date_time_t previous_date_time;
|
||||||
|
watch_date_time_t date_time = movement_get_local_date_time();
|
||||||
|
movement_clock_mode_t clock_mode_24h = movement_clock_mode_24h();
|
||||||
char buf[6 + 1];
|
char buf[6 + 1];
|
||||||
|
|
||||||
// If the hour needs updating or it's the first time displaying the time
|
// If the hour needs updating or it's the first time displaying the time
|
||||||
if ((game_state.curr_screen != SCREEN_TIME) || (date_time.unit.hour != previous_date_time.unit.hour)) {
|
if ((game_state.curr_screen != SCREEN_TIME) || (date_time.unit.hour != previous_date_time.unit.hour)) {
|
||||||
uint8_t hour = date_time.unit.hour;
|
uint8_t hour = date_time.unit.hour;
|
||||||
game_state.curr_screen = SCREEN_TIME;
|
game_state.curr_screen = SCREEN_TIME;
|
||||||
|
if (!watch_sleep_animation_is_running()) {
|
||||||
if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
|
watch_set_colon();
|
||||||
|
watch_start_indicator_blink_if_possible(WATCH_INDICATOR_COLON, 500);
|
||||||
|
}
|
||||||
|
if (clock_mode_24h != MOVEMENT_CLOCK_MODE_12H) watch_set_indicator(WATCH_INDICATOR_24H);
|
||||||
else {
|
else {
|
||||||
if (hour >= 12) watch_set_indicator(WATCH_INDICATOR_PM);
|
if (hour >= 12) watch_set_indicator(WATCH_INDICATOR_PM);
|
||||||
hour %= 12;
|
hour %= 12;
|
||||||
if (hour == 0) hour = 12;
|
if (hour == 0) hour = 12;
|
||||||
}
|
}
|
||||||
watch_set_colon();
|
sprintf( buf, clock_mode_24h == MOVEMENT_CLOCK_MODE_024H ? "%02d%02d " : "%2d%02d ", hour, date_time.unit.minute);
|
||||||
sprintf( buf, "%2d%02d ", hour, date_time.unit.minute);
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
watch_display_string(buf, 4);
|
|
||||||
}
|
}
|
||||||
// If both digits of the minute need updating
|
// If only the minute need updating
|
||||||
else if ((date_time.unit.minute / 10) != (previous_date_time.unit.minute / 10)) {
|
else {
|
||||||
sprintf( buf, "%02d ", date_time.unit.minute);
|
sprintf( buf, "%02d", date_time.unit.minute);
|
||||||
watch_display_string(buf, 6);
|
watch_display_text(WATCH_POSITION_MINUTES, buf);
|
||||||
}
|
|
||||||
// If only the ones-place of the minute needs updating.
|
|
||||||
else if (date_time.unit.minute != previous_date_time.unit.minute) {
|
|
||||||
sprintf( buf, "%d ", date_time.unit.minute % 10);
|
|
||||||
watch_display_string(buf, 7);
|
|
||||||
}
|
}
|
||||||
previous_date_time.reg = date_time.reg;
|
previous_date_time.reg = date_time.reg;
|
||||||
}
|
}
|
||||||
@ -356,36 +404,37 @@ static void begin_playing(endless_runner_state_t *state) {
|
|||||||
uint8_t difficulty = state -> difficulty;
|
uint8_t difficulty = state -> difficulty;
|
||||||
game_state.curr_screen = SCREEN_PLAYING;
|
game_state.curr_screen = SCREEN_PLAYING;
|
||||||
watch_clear_colon();
|
watch_clear_colon();
|
||||||
|
display_sound_indicator(state -> soundOn);
|
||||||
movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ);
|
movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ);
|
||||||
if (game_state.fuel_mode) {
|
if (game_state.fuel_mode) {
|
||||||
watch_display_string(" ", 0);
|
watch_clear_display();
|
||||||
game_state.obst_pattern = get_random_fuel(0);
|
game_state.obst_pattern = get_random_fuel(0);
|
||||||
if ((16 * JUMP_FRAMES_FUEL_RECHARGE) < JUMP_FRAMES_FUEL) // 16 frames of zeros at the start of a level
|
if ((16 * JUMP_FRAMES_FUEL_RECHARGE) < JUMP_FRAMES_FUEL) // 16 frames of zeros at the start of a level
|
||||||
game_state.fuel = JUMP_FRAMES_FUEL - (16 * JUMP_FRAMES_FUEL_RECHARGE); // Have it below its max to show it counting up when starting.
|
game_state.fuel = JUMP_FRAMES_FUEL - (16 * JUMP_FRAMES_FUEL_RECHARGE); // Have it below its max to show it counting up when starting.
|
||||||
if (game_state.fuel < JUMP_FRAMES_FUEL_RECHARGE) game_state.fuel = JUMP_FRAMES_FUEL_RECHARGE;
|
if (game_state.fuel < JUMP_FRAMES_FUEL_RECHARGE) game_state.fuel = JUMP_FRAMES_FUEL_RECHARGE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
watch_display_string(" ", 2);
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, " ");
|
||||||
game_state.obst_pattern = get_random_legal(0, difficulty);
|
game_state.obst_pattern = get_random_legal(0, difficulty);
|
||||||
}
|
}
|
||||||
game_state.jump_state = NOT_JUMPING;
|
game_state.jump_state = NOT_JUMPING;
|
||||||
display_ball(game_state.jump_state != NOT_JUMPING);
|
display_ball(game_state.jump_state != NOT_JUMPING);
|
||||||
display_score( game_state.curr_score);
|
display_score( game_state.curr_score);
|
||||||
if (state -> soundOn){
|
if (state -> soundOn){
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_C5, 200);
|
watch_buzzer_play_sequence(start_tune, NULL);
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_E5, 200);
|
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_G5, 200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void display_lose_screen(endless_runner_state_t *state) {
|
static void display_lose_screen(endless_runner_state_t *state) {
|
||||||
game_state.curr_screen = SCREEN_LOSE;
|
game_state.curr_screen = SCREEN_LOSE;
|
||||||
game_state.curr_score = 0;
|
game_state.curr_score = 0;
|
||||||
watch_display_string(" LOSE ", 0);
|
watch_clear_display();
|
||||||
if (state -> soundOn)
|
watch_display_text(WATCH_POSITION_BOTTOM, " LOSE ");
|
||||||
watch_buzzer_play_note(BUZZER_NOTE_A1, 600);
|
if (state -> soundOn) {
|
||||||
else
|
watch_buzzer_play_sequence(lose_tune, NULL);
|
||||||
delay_ms(600);
|
delay_ms(600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) {
|
static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) {
|
||||||
@ -395,9 +444,9 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t
|
|||||||
case 2:
|
case 2:
|
||||||
game_state.loc_2_on = obstacle;
|
game_state.loc_2_on = obstacle;
|
||||||
if (obstacle)
|
if (obstacle)
|
||||||
watch_set_pixel(0, 20);
|
watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
else if (game_state.jump_state != NOT_JUMPING) {
|
else if (game_state.jump_state != NOT_JUMPING) {
|
||||||
watch_clear_pixel(0, 20);
|
watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
if (game_state.fuel_mode && prev_obst_pos_two)
|
if (game_state.fuel_mode && prev_obst_pos_two)
|
||||||
add_to_score(state);
|
add_to_score(state);
|
||||||
}
|
}
|
||||||
@ -406,55 +455,20 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t
|
|||||||
case 3:
|
case 3:
|
||||||
game_state.loc_3_on = obstacle;
|
game_state.loc_3_on = obstacle;
|
||||||
if (obstacle)
|
if (obstacle)
|
||||||
watch_set_pixel(1, 21);
|
watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
else if (game_state.jump_state != NOT_JUMPING)
|
else if (game_state.jump_state != NOT_JUMPING)
|
||||||
watch_clear_pixel(1, 21);
|
watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
if (!game_state.fuel_mode && obstacle) // If an obstacle is here, it means the ball cleared it
|
if (!game_state.fuel_mode && obstacle) // If an obstacle is here, it means the ball cleared it
|
||||||
add_to_score(state);
|
add_to_score(state);
|
||||||
//fall through
|
//fall through
|
||||||
case 0:
|
|
||||||
case 5:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(0, 18 + grid_loc);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(0, 18 + grid_loc);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(1, 22);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(1, 22);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(1, 0);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(1, 0);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
case 8:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(0, grid_loc - 6);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(0, grid_loc - 6);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
case 10:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(0, grid_loc - 5);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(0, grid_loc - 5);
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
if (obstacle)
|
|
||||||
watch_set_pixel(1, 6);
|
|
||||||
else
|
|
||||||
watch_clear_pixel(1, 6);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
|
if (obstacle)
|
||||||
|
watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
|
else
|
||||||
|
watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,26 +560,37 @@ void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr) {
|
|||||||
memset(*context_ptr, 0, sizeof(endless_runner_state_t));
|
memset(*context_ptr, 0, sizeof(endless_runner_state_t));
|
||||||
endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr;
|
endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr;
|
||||||
state->difficulty = DIFF_NORM;
|
state->difficulty = DIFF_NORM;
|
||||||
|
state->tap_control_on = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void endless_runner_face_activate(void *context) {
|
void endless_runner_face_activate(void *context) {
|
||||||
(void) context;
|
(void) context;
|
||||||
|
bool is_custom_lcd = watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM;
|
||||||
|
ball_arr_com = is_custom_lcd ? custom_ball_arr_com : classic_ball_arr_com;
|
||||||
|
ball_arr_seg = is_custom_lcd ? custom_ball_arr_seg : classic_ball_arr_seg;
|
||||||
|
obstacle_arr_com = is_custom_lcd ? custom_obstacle_arr_com : classic_obstacle_arr_com;
|
||||||
|
obstacle_arr_seg = is_custom_lcd ? custom_obstacle_arr_seg : classic_obstacle_arr_seg;
|
||||||
|
if (watch_sleep_animation_is_running()) {
|
||||||
|
watch_stop_blink();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool endless_runner_face_loop(movement_event_t event, void *context) {
|
bool endless_runner_face_loop(movement_event_t event, void *context) {
|
||||||
endless_runner_state_t *state = (endless_runner_state_t *)context;
|
endless_runner_state_t *state = (endless_runner_state_t *)context;
|
||||||
switch (event.event_type) {
|
switch (event.event_type) {
|
||||||
case EVENT_ACTIVATE:
|
case EVENT_ACTIVATE:
|
||||||
|
disable_tap_control(state);
|
||||||
check_and_reset_hi_score(state);
|
check_and_reset_hi_score(state);
|
||||||
if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL);
|
|
||||||
display_title(state);
|
display_title(state);
|
||||||
break;
|
break;
|
||||||
case EVENT_TICK:
|
case EVENT_TICK:
|
||||||
switch (game_state.curr_screen)
|
switch (game_state.curr_screen)
|
||||||
{
|
{
|
||||||
case SCREEN_TITLE:
|
case SCREEN_TITLE:
|
||||||
|
case SCREEN_SCORE:
|
||||||
case SCREEN_LOSE:
|
case SCREEN_LOSE:
|
||||||
|
case SCREEN_TIME:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
update_game(state, event.subsecond);
|
update_game(state, event.subsecond);
|
||||||
@ -574,15 +599,37 @@ bool endless_runner_face_loop(movement_event_t event, void *context) {
|
|||||||
break;
|
break;
|
||||||
case EVENT_LIGHT_BUTTON_UP:
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
case EVENT_ALARM_BUTTON_UP:
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
if (game_state.curr_screen == SCREEN_TITLE)
|
switch (game_state.curr_screen) {
|
||||||
begin_playing(state);
|
case SCREEN_SCORE:
|
||||||
else if (game_state.curr_screen == SCREEN_LOSE)
|
enable_tap_control(state);
|
||||||
display_title(state);
|
begin_playing(state);
|
||||||
|
break;
|
||||||
|
case SCREEN_TITLE:
|
||||||
|
enable_tap_control(state);
|
||||||
|
// fall through
|
||||||
|
case SCREEN_TIME:
|
||||||
|
case SCREEN_LOSE:
|
||||||
|
watch_clear_display();
|
||||||
|
display_score_screen(state);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EVENT_LIGHT_LONG_PRESS:
|
case EVENT_LIGHT_LONG_PRESS:
|
||||||
if (game_state.curr_screen == SCREEN_TITLE)
|
if (game_state.curr_screen == SCREEN_SCORE)
|
||||||
change_difficulty(state);
|
change_difficulty(state);
|
||||||
break;
|
break;
|
||||||
|
case EVENT_SINGLE_TAP:
|
||||||
|
case EVENT_DOUBLE_TAP:
|
||||||
|
if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes
|
||||||
|
// Allow starting a new game by tapping.
|
||||||
|
if (game_state.curr_screen == SCREEN_SCORE) {
|
||||||
|
begin_playing(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (game_state.curr_screen == SCREEN_LOSE) {
|
||||||
|
display_score_screen(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//fall through
|
||||||
case EVENT_LIGHT_BUTTON_DOWN:
|
case EVENT_LIGHT_BUTTON_DOWN:
|
||||||
case EVENT_ALARM_BUTTON_DOWN:
|
case EVENT_ALARM_BUTTON_DOWN:
|
||||||
if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){
|
if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){
|
||||||
@ -592,15 +639,21 @@ bool endless_runner_face_loop(movement_event_t event, void *context) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EVENT_ALARM_LONG_PRESS:
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
if (game_state.curr_screen != SCREEN_PLAYING)
|
if (game_state.curr_screen == SCREEN_TITLE || game_state.curr_screen == SCREEN_SCORE)
|
||||||
toggle_sound(state);
|
toggle_sound(state);
|
||||||
break;
|
break;
|
||||||
case EVENT_TIMEOUT:
|
case EVENT_TIMEOUT:
|
||||||
if (game_state.curr_screen != SCREEN_TITLE)
|
disable_tap_control(state);
|
||||||
display_title(state);
|
if (game_state.curr_screen != SCREEN_SCORE)
|
||||||
|
display_score_screen(state);
|
||||||
break;
|
break;
|
||||||
case EVENT_LOW_ENERGY_UPDATE:
|
case EVENT_LOW_ENERGY_UPDATE:
|
||||||
display_time(watch_rtc_get_date_time(), movement_clock_mode_24h());
|
if (game_state.curr_screen != SCREEN_TIME) {
|
||||||
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN ", "ER ");
|
||||||
|
display_sound_indicator(state -> soundOn);
|
||||||
|
display_difficulty(state->difficulty);
|
||||||
|
}
|
||||||
|
display_time();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return movement_default_loop_handler(event);
|
return movement_default_loop_handler(event);
|
||||||
@ -609,6 +662,6 @@ bool endless_runner_face_loop(movement_event_t event, void *context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void endless_runner_face_resign(void *context) {
|
void endless_runner_face_resign(void *context) {
|
||||||
(void) context;
|
endless_runner_state_t *state = (endless_runner_state_t *)context;
|
||||||
|
disable_tap_control(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +33,8 @@
|
|||||||
This is a basic endless-runner, like the [Chrome Dino game](https://en.wikipedia.org/wiki/Dinosaur_Game).
|
This is a basic endless-runner, like the [Chrome Dino game](https://en.wikipedia.org/wiki/Dinosaur_Game).
|
||||||
On the title screen, you can select a difficulty by long-pressing LIGHT or toggle sound by long-pressing ALARM.
|
On the title screen, you can select a difficulty by long-pressing LIGHT or toggle sound by long-pressing ALARM.
|
||||||
LED or ALARM are used to jump.
|
LED or ALARM are used to jump.
|
||||||
|
If the accelerometer is installed, you can tap the screen to jump and move through the menus after using the
|
||||||
|
buttons to go into the first game.
|
||||||
High-score is displayed on the top-right on the title screen. During a game, the current score is displayed.
|
High-score is displayed on the top-right on the title screen. During a game, the current score is displayed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -42,7 +44,8 @@ typedef struct {
|
|||||||
uint8_t month_last_hi_score : 4;
|
uint8_t month_last_hi_score : 4;
|
||||||
uint8_t year_last_hi_score : 6;
|
uint8_t year_last_hi_score : 6;
|
||||||
uint8_t soundOn : 1;
|
uint8_t soundOn : 1;
|
||||||
/* 24 bits, likely aligned to 32 bits = 4 bytes */
|
uint8_t tap_control_on : 1;
|
||||||
|
uint8_t unused : 7;
|
||||||
} endless_runner_state_t;
|
} endless_runner_state_t;
|
||||||
|
|
||||||
void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr);
|
void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr);
|
||||||
@ -30,19 +30,22 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "higher_lower_game_face.h"
|
#include "higher_lower_game_face.h"
|
||||||
#include "watch_private_display.h"
|
#include "watch_common_display.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define KING 12
|
||||||
|
#define QUEEN 11
|
||||||
|
#define JACK 10
|
||||||
|
|
||||||
#define TITLE_TEXT "Hi-Lo"
|
#define TITLE_TEXT "Hi-Lo"
|
||||||
#define GAME_BOARD_SIZE 6
|
#define GAME_BOARD_SIZE 6
|
||||||
#define MAX_BOARDS 40
|
#define MAX_BOARDS 40
|
||||||
#define GUESSES_PER_SCREEN 5
|
#define GUESSES_PER_SCREEN 5
|
||||||
#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN)
|
#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN)
|
||||||
#define STATUS_DISPLAY_START 0
|
|
||||||
#define BOARD_SCORE_DISPLAY_START 2
|
|
||||||
#define BOARD_DISPLAY_START 4
|
#define BOARD_DISPLAY_START 4
|
||||||
#define BOARD_DISPLAY_END 9
|
#define BOARD_DISPLAY_END 9
|
||||||
#define MIN_CARD_VALUE 2
|
#define MIN_CARD_VALUE 2
|
||||||
#define MAX_CARD_VALUE 14
|
#define MAX_CARD_VALUE KING
|
||||||
#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)
|
#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)
|
||||||
#define CARD_SUIT_COUNT 4
|
#define CARD_SUIT_COUNT 4
|
||||||
#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)
|
#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)
|
||||||
@ -111,7 +114,6 @@ static void shuffle_deck(void) {
|
|||||||
|
|
||||||
static void reset_deck(void) {
|
static void reset_deck(void) {
|
||||||
current_card = 0;
|
current_card = 0;
|
||||||
stack_deck();
|
|
||||||
shuffle_deck();
|
shuffle_deck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,8 +143,8 @@ static void reset_board(bool first_round) {
|
|||||||
|
|
||||||
static void init_game(void) {
|
static void init_game(void) {
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT);
|
||||||
watch_display_string("GA", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "HL");
|
||||||
reset_deck();
|
reset_deck();
|
||||||
reset_board(true);
|
reset_board(true);
|
||||||
score = 0;
|
score = 0;
|
||||||
@ -151,16 +153,23 @@ static void init_game(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void set_segment_at_position(segment_t segment, uint8_t position) {
|
static void set_segment_at_position(segment_t segment, uint8_t position) {
|
||||||
const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF;
|
digit_mapping_t segmap;
|
||||||
const uint8_t com_pin = position_segment_data >> 6;
|
if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) {
|
||||||
const uint8_t seg = position_segment_data & 0x3F;
|
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);
|
watch_set_pixel(com_pin, seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline size_t get_display_position(size_t board_position) {
|
||||||
|
return FLIP_BOARD_DIRECTION ? BOARD_DISPLAY_START + board_position : BOARD_DISPLAY_END - board_position;
|
||||||
|
}
|
||||||
|
|
||||||
static void render_board_position(size_t board_position) {
|
static void render_board_position(size_t board_position) {
|
||||||
const size_t display_position = FLIP_BOARD_DIRECTION
|
const size_t display_position = get_display_position(board_position);
|
||||||
? BOARD_DISPLAY_START + board_position
|
|
||||||
: BOARD_DISPLAY_END - board_position;
|
|
||||||
const bool revealed = game_board[board_position].revealed;
|
const bool revealed = game_board[board_position].revealed;
|
||||||
|
|
||||||
//// Current position indicator spot
|
//// Current position indicator spot
|
||||||
@ -178,18 +187,18 @@ static void render_board_position(size_t board_position) {
|
|||||||
|
|
||||||
const uint8_t value = game_board[board_position].value;
|
const uint8_t value = game_board[board_position].value;
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 14: // A (≡)
|
case KING: // K (≡)
|
||||||
watch_display_character(' ', display_position);
|
watch_display_character(' ', display_position);
|
||||||
set_segment_at_position(A, display_position);
|
set_segment_at_position(A, display_position);
|
||||||
set_segment_at_position(D, display_position);
|
set_segment_at_position(D, display_position);
|
||||||
set_segment_at_position(G, display_position);
|
set_segment_at_position(G, display_position);
|
||||||
break;
|
break;
|
||||||
case 13: // K (=)
|
case QUEEN: // Q (=)
|
||||||
watch_display_character(' ', display_position);
|
watch_display_character(' ', display_position);
|
||||||
set_segment_at_position(A, display_position);
|
set_segment_at_position(A, display_position);
|
||||||
set_segment_at_position(D, display_position);
|
set_segment_at_position(D, display_position);
|
||||||
break;
|
break;
|
||||||
case 12: // Q (-)
|
case JACK: // J (-)
|
||||||
watch_display_character('-', display_position);
|
watch_display_character('-', display_position);
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
@ -209,16 +218,16 @@ static void render_board_count(void) {
|
|||||||
// Render completed boards (screens)
|
// Render completed boards (screens)
|
||||||
char buf[3] = {0};
|
char buf[3] = {0};
|
||||||
snprintf(buf, sizeof(buf), "%2hhu", completed_board_count);
|
snprintf(buf, sizeof(buf), "%2hhu", completed_board_count);
|
||||||
watch_display_string(buf, BOARD_SCORE_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_final_score(void) {
|
static void render_final_score(void) {
|
||||||
watch_display_string("SC", STATUS_DISPLAY_START);
|
watch_display_text_with_fallback(WATCH_POSITION_TOP, "SCORE", "SC ");
|
||||||
char buf[7] = {0};
|
char buf[7] = {0};
|
||||||
const uint8_t complete_boards = score / GUESSES_PER_SCREEN;
|
const uint8_t complete_boards = score / GUESSES_PER_SCREEN;
|
||||||
snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score);
|
snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score);
|
||||||
watch_set_colon();
|
watch_set_colon();
|
||||||
watch_display_string(buf, BOARD_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static guess_t get_answer(void) {
|
static guess_t get_answer(void) {
|
||||||
@ -251,13 +260,13 @@ static void do_game_loop(guess_t user_guess) {
|
|||||||
// Render answer indicator
|
// Render answer indicator
|
||||||
switch (answer) {
|
switch (answer) {
|
||||||
case HL_GUESS_EQUAL:
|
case HL_GUESS_EQUAL:
|
||||||
watch_display_string("==", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "==");
|
||||||
break;
|
break;
|
||||||
case HL_GUESS_HIGHER:
|
case HL_GUESS_HIGHER:
|
||||||
watch_display_string("HI", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "HI");
|
||||||
break;
|
break;
|
||||||
case HL_GUESS_LOWER:
|
case HL_GUESS_LOWER:
|
||||||
watch_display_string("LO", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "LO");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,18 +277,22 @@ static void do_game_loop(guess_t user_guess) {
|
|||||||
// No score for two consecutive identical cards
|
// No score for two consecutive identical cards
|
||||||
} else {
|
} else {
|
||||||
// Incorrect guess, game over
|
// Incorrect guess, game over
|
||||||
watch_display_string("GO", STATUS_DISPLAY_START);
|
watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "End", "GO");
|
||||||
game_board[guess_position].revealed = true;
|
game_board[guess_position].revealed = true;
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, "------");
|
||||||
|
render_board_position(guess_position - 1);
|
||||||
render_board_position(guess_position);
|
render_board_position(guess_position);
|
||||||
|
if (game_board[guess_position].value == JACK && guess_position < GAME_BOARD_SIZE) // Adds a space in case the revealed option is '-'
|
||||||
|
watch_display_character(' ', get_display_position(guess_position + 1));
|
||||||
game_state = HL_GS_LOSE;
|
game_state = HL_GS_LOSE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score >= WIN_SCORE) {
|
if (score >= WIN_SCORE) {
|
||||||
// Win, perhaps some kind of animation sequence?
|
// Win, perhaps some kind of animation sequence?
|
||||||
watch_display_string("WI", STATUS_DISPLAY_START);
|
watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "WIN", "WI");
|
||||||
watch_display_string(" ", BOARD_SCORE_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||||
watch_display_string("------", BOARD_DISPLAY_START);
|
watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WINNER", "winnEr");
|
||||||
game_state = HL_GS_WIN;
|
game_state = HL_GS_WIN;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -309,12 +322,12 @@ static void do_game_loop(guess_t user_guess) {
|
|||||||
break;
|
break;
|
||||||
case HL_GS_SHOW_SCORE:
|
case HL_GS_SHOW_SCORE:
|
||||||
watch_clear_display();
|
watch_clear_display();
|
||||||
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT);
|
||||||
watch_display_string("GA", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "HL");
|
||||||
game_state = HL_GS_TITLE_SCREEN;
|
game_state = HL_GS_TITLE_SCREEN;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
watch_display_string("ERROR", BOARD_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_BOTTOM, "ERROR");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,6 +357,7 @@ void higher_lower_game_face_activate(void *context) {
|
|||||||
(void) state;
|
(void) state;
|
||||||
// Handle any tasks related to your watch face coming on screen.
|
// Handle any tasks related to your watch face coming on screen.
|
||||||
game_state = HL_GS_TITLE_SCREEN;
|
game_state = HL_GS_TITLE_SCREEN;
|
||||||
|
stack_deck();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool higher_lower_game_face_loop(movement_event_t event, void *context) {
|
bool higher_lower_game_face_loop(movement_event_t event, void *context) {
|
||||||
@ -353,8 +367,8 @@ bool higher_lower_game_face_loop(movement_event_t event, void *context) {
|
|||||||
switch (event.event_type) {
|
switch (event.event_type) {
|
||||||
case EVENT_ACTIVATE:
|
case EVENT_ACTIVATE:
|
||||||
// Show your initial UI here.
|
// Show your initial UI here.
|
||||||
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT);
|
||||||
watch_display_string("GA", STATUS_DISPLAY_START);
|
watch_display_text(WATCH_POSITION_TOP_LEFT, "HL");
|
||||||
break;
|
break;
|
||||||
case EVENT_TICK:
|
case EVENT_TICK:
|
||||||
// If needed, update your display here.
|
// If needed, update your display here.
|
||||||
577
watch-faces/complication/lander_face.c
Normal file
577
watch-faces/complication/lander_face.c
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Klingon Jane
|
||||||
|
*
|
||||||
|
* 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 <math.h>
|
||||||
|
#include "lander_face.h"
|
||||||
|
#include "watch_common_display.h"
|
||||||
|
|
||||||
|
#ifndef max
|
||||||
|
#define max(x, y) ((y) > (x) ? (y) : (x))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef min
|
||||||
|
#define min(x, y) ((x) > (y) ? (y) : (x))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LANDER_TICK_FREQUENCY 8
|
||||||
|
#define MONSTER_DISPLAY_TICKS 9
|
||||||
|
#define ENGINE_THRUST 11
|
||||||
|
#define MODE_WAITING_TO_START 0
|
||||||
|
#define MODE_DISPLAY_SKILL_LEVEL 1
|
||||||
|
#define MODE_PLAYING 2
|
||||||
|
#define MODE_TOUCHDOWN_BLANK 3
|
||||||
|
#define MODE_DISPLAY_FINAL_STATUS 4
|
||||||
|
#define MODE_MONSTER 5
|
||||||
|
#define MODE_FIND_EARTH_MESSAGE 6
|
||||||
|
#define CREWS_COMPLIMENT 13
|
||||||
|
// Granularity is divisions per foot - height display
|
||||||
|
#define GRANUL 40
|
||||||
|
// Next lines for repeat heroes only.
|
||||||
|
#define PROMOTION_INTERVAL 3
|
||||||
|
#define LEVEL_ACE 8
|
||||||
|
#define LEVEL_STARBUCK 11
|
||||||
|
#define HARD_EARTH_INCREMENTS 11
|
||||||
|
#define MAX_HARD_EARTH_CHANCE 6
|
||||||
|
|
||||||
|
// The gory final result calculations:
|
||||||
|
#define SPEED_FATALITY_ALL 41
|
||||||
|
#define SPEED_FATALITY_NONE 26
|
||||||
|
#define SPEED_NO_DAMAGE 21
|
||||||
|
#define SPEED_LEVEL_INCREMENTS 2
|
||||||
|
#define SPEED_MAJOR_CRASH 73
|
||||||
|
#define MAJOR_CRASH_INCREMENTS 65
|
||||||
|
#define SPEED_INJURY_NONE 20
|
||||||
|
#define SPEED_INJURY_FULCRUM 32
|
||||||
|
#define INJURY_FULCRUM_PROB 65
|
||||||
|
#define FUEL_SCORE_GOOD 145
|
||||||
|
#define FUEL_SCORE_GREAT 131
|
||||||
|
#define FUEL_SCORE_FANTASTIC 125
|
||||||
|
|
||||||
|
// Joey Castillo to oversee storage allocation row
|
||||||
|
#define LANDER_STORAGE_ROW 2
|
||||||
|
#define STORAGE_KEY_NUMBER 110
|
||||||
|
|
||||||
|
#define DIFFICULTY_LEVELS 3
|
||||||
|
char lander_difficulty_names[DIFFICULTY_LEVELS][7] = {
|
||||||
|
"NOrMAL", "HArd ", "HArdEr"
|
||||||
|
};
|
||||||
|
#define MONSTER_TYPES 4
|
||||||
|
char lander_monster_names[MONSTER_TYPES][7] = {
|
||||||
|
"mOnStr", "6Erbil", "HAmStr", "Rabbit"
|
||||||
|
};
|
||||||
|
#define MONSTER_ACTIONS 8
|
||||||
|
char lander_monster_actions[MONSTER_ACTIONS][7] = {
|
||||||
|
"HUn6ry", " EAtS", "6Reedy", "annoYd", "nASty ", "SAVOry", "HO66SH", " pI66Y"
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Custom methods
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
|
||||||
|
static int gen_random_int (int16_t lower, int16_t upper) {
|
||||||
|
int range;
|
||||||
|
int retVal;
|
||||||
|
range = upper - lower + 1;
|
||||||
|
if ( range < 2 ) range = 2;
|
||||||
|
// Emulator: use rand. Hardware: use arc4random.
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
retVal = rand() % range;
|
||||||
|
#else
|
||||||
|
retVal = arc4random_uniform(range);
|
||||||
|
#endif
|
||||||
|
retVal += lower;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t assignProb ( uint8_t lowerProb, uint8_t upperProb, int16_t lowerSpeed, int16_t upperSpeed, int16_t actSpeed ) {
|
||||||
|
float probRange, speedRange;
|
||||||
|
float ratio, probFloat;
|
||||||
|
int probInt;
|
||||||
|
speedRange = upperSpeed - lowerSpeed;
|
||||||
|
if (speedRange<1.0) speedRange = 1.0;
|
||||||
|
probRange = upperProb - lowerProb;
|
||||||
|
ratio = ( (float) actSpeed - (float) lowerSpeed ) / speedRange;
|
||||||
|
probFloat = (float) lowerProb + ( ratio * probRange );
|
||||||
|
probInt = (int) ( probFloat + 0.5 );
|
||||||
|
probInt = min ( probInt, upperProb );
|
||||||
|
probInt = max ( probInt, lowerProb );
|
||||||
|
return (uint8_t) probInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_to_lander_EEPROM(lander_state_t *state) {
|
||||||
|
uint8_t output_array [ 3 ];
|
||||||
|
output_array [ 0 ] = STORAGE_KEY_NUMBER;
|
||||||
|
output_array [ 1 ] = state->hero_counter;
|
||||||
|
output_array [ 2 ] = state->legend_counter;
|
||||||
|
watch_storage_erase ( LANDER_STORAGE_ROW );
|
||||||
|
watch_storage_sync ( );
|
||||||
|
watch_storage_write ( LANDER_STORAGE_ROW, 0, output_array, 3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Standard watch face methods
|
||||||
|
// ---------------------------
|
||||||
|
void lander_face_setup(uint8_t watch_face_index, void ** context_ptr) {
|
||||||
|
(void) watch_face_index;
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(lander_state_t));
|
||||||
|
memset(*context_ptr, 0, sizeof(lander_state_t));
|
||||||
|
lander_state_t *state = (lander_state_t *)*context_ptr;
|
||||||
|
state->led_enabled = false;
|
||||||
|
}
|
||||||
|
// Emulator only: Seed random number generator
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
srand(time(NULL));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void lander_face_activate(void *context) {
|
||||||
|
lander_state_t *state = (lander_state_t *)context;
|
||||||
|
char buf [ 7 ];
|
||||||
|
state->mode = MODE_WAITING_TO_START;
|
||||||
|
state->led_active = false;
|
||||||
|
state->reset_counter = 0;
|
||||||
|
watch_clear_all_indicators ( );
|
||||||
|
uint32_t offset = 0;
|
||||||
|
uint32_t size = 3;
|
||||||
|
uint8_t stored_data [ size ];
|
||||||
|
// See if the hero_counter was ever written to EEPROM storage
|
||||||
|
watch_storage_read (LANDER_STORAGE_ROW, offset, stored_data, size);
|
||||||
|
if (stored_data[0] == STORAGE_KEY_NUMBER )
|
||||||
|
{
|
||||||
|
state->hero_counter = stored_data [1]; // There's real data in there.
|
||||||
|
state->legend_counter = stored_data [2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state->hero_counter = 0; // Nope. Nothing there.
|
||||||
|
state->legend_counter = 0;
|
||||||
|
write_to_lander_EEPROM(state); // Initial EEPROM tracking data.
|
||||||
|
}
|
||||||
|
state->difficulty_level = state->hero_counter / PROMOTION_INTERVAL;
|
||||||
|
state->difficulty_level = min ( state->difficulty_level, DIFFICULTY_LEVELS - 1 ); // Upper limit
|
||||||
|
// Fancy intro
|
||||||
|
if ( state->legend_counter == 0 ) watch_display_text(WATCH_POSITION_TOP_LEFT, "LA");
|
||||||
|
else watch_display_text(WATCH_POSITION_TOP_LEFT, "LE");
|
||||||
|
if ( ( state->hero_counter == 0 ) || ( state->hero_counter >= 40 ) ) watch_display_text ( WATCH_POSITION_TOP_RIGHT, " ");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf ( buf, "%2d", state->hero_counter );
|
||||||
|
watch_display_text ( WATCH_POSITION_TOP_RIGHT, buf);
|
||||||
|
}
|
||||||
|
if ( state->hero_counter >= 100 ) sprintf ( buf, "Str%3d", state->hero_counter );
|
||||||
|
else if ( state->hero_counter >= 40 ) sprintf ( buf, "Strb%2d", state->hero_counter );
|
||||||
|
else if ( state->hero_counter >= LEVEL_STARBUCK ) sprintf ( buf, "StrbUC" );
|
||||||
|
else if ( state->hero_counter >= LEVEL_ACE ) sprintf ( buf, " ACE " ); // This human is good
|
||||||
|
else if ( state->difficulty_level == 0 ) sprintf ( buf, " " );
|
||||||
|
else sprintf ( buf, "%s", lander_difficulty_names[state->difficulty_level] );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf);
|
||||||
|
if (state->led_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lander_face_loop(movement_event_t event, void *context) {
|
||||||
|
lander_state_t *state = (lander_state_t *)context;
|
||||||
|
char buf [ 20 ]; // [11] is more correct and works; compiler too helpful.
|
||||||
|
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_TICK:
|
||||||
|
state->tick_counter++;
|
||||||
|
if ( state->mode == MODE_PLAYING ) {
|
||||||
|
int16_t accel = state->gravity;
|
||||||
|
bool gas_pedal_on = HAL_GPIO_BTN_ALARM_read() || HAL_GPIO_BTN_LIGHT_read();
|
||||||
|
if ( gas_pedal_on && ( state->fuel_remaining > 0 ) ) {
|
||||||
|
accel = ENGINE_THRUST + state->gravity; // Gravity is negative
|
||||||
|
state->fuel_remaining--; // Used 1 fuel unit
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_LAP );
|
||||||
|
// Low fuel warning indicators
|
||||||
|
if ( state->fuel_remaining == ( 3 * LANDER_TICK_FREQUENCY ) ) { // 3 seconds of fuel left
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_SIGNAL );
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_BELL );
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_PM );
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_24H );
|
||||||
|
}
|
||||||
|
else if ( state->fuel_remaining == 0 ) { // 0 seconds of fuel left, empty!
|
||||||
|
watch_clear_all_indicators ( );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
watch_clear_indicator ( WATCH_INDICATOR_LAP );
|
||||||
|
}
|
||||||
|
state->speed += accel;
|
||||||
|
state->height += state->speed;
|
||||||
|
if ( state->height > 971 * 80 ) { // Escape height
|
||||||
|
watch_clear_all_indicators ();
|
||||||
|
watch_display_text( WATCH_POSITION_BOTTOM, "ESCAPE" );
|
||||||
|
state->tick_counter = 0;
|
||||||
|
state->mode = MODE_WAITING_TO_START;
|
||||||
|
}
|
||||||
|
else if ( state->height <= 0 ) { // Touchdown
|
||||||
|
state->tick_counter = 0;
|
||||||
|
state->mode = MODE_TOUCHDOWN_BLANK;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Update height display
|
||||||
|
sprintf ( buf, "%4d", (int) ( state->height / GRANUL ) );
|
||||||
|
watch_display_text( WATCH_POSITION_BOTTOM, buf );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( state->mode == MODE_TOUCHDOWN_BLANK ) {
|
||||||
|
// Blank display on touchdown
|
||||||
|
if ( state->tick_counter == 1 ) {
|
||||||
|
watch_clear_all_indicators ();
|
||||||
|
watch_display_text( WATCH_POSITION_BOTTOM, " " );
|
||||||
|
|
||||||
|
// Also calc fuel score now.
|
||||||
|
float fuel_score_float;
|
||||||
|
uint16_t fuel_used;
|
||||||
|
fuel_used = state->fuel_start - state->fuel_remaining;
|
||||||
|
fuel_score_float = (float) fuel_used / (float) state->fuel_tpl;
|
||||||
|
state->fuel_score = (int) (fuel_score_float * 100.0 + 0.5);
|
||||||
|
if ( state->legend_counter == 0 ) state->fuel_score -= 8; // First Earth is easier
|
||||||
|
// Monitor reset_counter
|
||||||
|
if ( fuel_used >= 1 ) state->reset_counter = 0;
|
||||||
|
else state->reset_counter++;
|
||||||
|
if ( state->reset_counter >= 3 ) {
|
||||||
|
state->hero_counter = 0;
|
||||||
|
state->difficulty_level = 0;
|
||||||
|
if ( state->reset_counter >= 6 ) state->legend_counter = 0;
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, "rESET ");
|
||||||
|
write_to_lander_EEPROM(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait until time for next display
|
||||||
|
if ( state->tick_counter >= ( 1 * LANDER_TICK_FREQUENCY ) ) {
|
||||||
|
state->tick_counter = 0;
|
||||||
|
state->mode = MODE_DISPLAY_FINAL_STATUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( state->mode == MODE_DISPLAY_FINAL_STATUS ) {
|
||||||
|
bool last_pass = false;
|
||||||
|
if ( state->tick_counter >= LANDER_TICK_FREQUENCY ) last_pass = true;
|
||||||
|
|
||||||
|
// Show final status
|
||||||
|
if ( state->tick_counter == 1 ) {
|
||||||
|
// Calculate many attributes
|
||||||
|
// 1) Major crash: bug, crater, vaporized (gone).
|
||||||
|
// 2) Rank ship's health 0 to 8
|
||||||
|
// 3) Crew fatalities and injuries
|
||||||
|
// 4) Special conditions: hero
|
||||||
|
// 5) Set fuel conservation indicators as appropriate
|
||||||
|
// 6) Set coffee maker OK indicator as appropriate
|
||||||
|
// 7) Green light if ship intact
|
||||||
|
// 8) Set standard display if not preempted.
|
||||||
|
bool allDone;
|
||||||
|
int16_t finalSpeed, boostedSpeed, levelsDamage;
|
||||||
|
int8_t shipsHealth, myRand;
|
||||||
|
uint8_t fatalities, probFatal, probInjury;
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
allDone = false;
|
||||||
|
// Easiest implementation for difficulty_level is to increase touchdown speed above actual.
|
||||||
|
finalSpeed = abs ( state->speed ) + state->difficulty_level * 4;
|
||||||
|
// First Earth is a bit easier than all the others
|
||||||
|
if ( state->legend_counter == 0 ) finalSpeed -= 2;
|
||||||
|
|
||||||
|
// 1) Major crash: bug, crater, vaporized (gone).
|
||||||
|
if ( finalSpeed >= SPEED_MAJOR_CRASH ) {
|
||||||
|
allDone = true;
|
||||||
|
shipsHealth = -1;
|
||||||
|
if ( finalSpeed >= ( SPEED_MAJOR_CRASH + 2 * MAJOR_CRASH_INCREMENTS ) ) sprintf ( buf, "GOnE " );
|
||||||
|
else if ( finalSpeed >= ( SPEED_MAJOR_CRASH + MAJOR_CRASH_INCREMENTS ) ) sprintf ( buf, " CrAtr" );
|
||||||
|
else sprintf ( buf, " bU6" );
|
||||||
|
}
|
||||||
|
// 2) Rank ship's health 0 to 8
|
||||||
|
if (!allDone) {
|
||||||
|
boostedSpeed = finalSpeed + SPEED_LEVEL_INCREMENTS - 1;
|
||||||
|
levelsDamage = (int) ( ( boostedSpeed - SPEED_NO_DAMAGE ) / SPEED_LEVEL_INCREMENTS );
|
||||||
|
shipsHealth = 8 - levelsDamage;
|
||||||
|
shipsHealth = min ( shipsHealth, 8 ); // Keep between 0 and 8
|
||||||
|
shipsHealth = max ( shipsHealth, 0 );
|
||||||
|
}
|
||||||
|
state->ships_health = shipsHealth; // Remember ships health
|
||||||
|
// 3) Crew fatalities and injuries
|
||||||
|
if (!allDone) {
|
||||||
|
// Fatalies
|
||||||
|
probFatal = assignProb ( 0, 92, SPEED_FATALITY_NONE, SPEED_FATALITY_ALL, finalSpeed );
|
||||||
|
// Injuries
|
||||||
|
if ( finalSpeed <= SPEED_INJURY_FULCRUM ) {
|
||||||
|
probInjury = assignProb ( 0, INJURY_FULCRUM_PROB, SPEED_INJURY_NONE, SPEED_INJURY_FULCRUM, finalSpeed );
|
||||||
|
} else {
|
||||||
|
probInjury = assignProb ( INJURY_FULCRUM_PROB, 96, SPEED_INJURY_FULCRUM, SPEED_FATALITY_ALL, finalSpeed );
|
||||||
|
}
|
||||||
|
fatalities = 0;
|
||||||
|
state->injured = 0;
|
||||||
|
for ( i = 0; i < CREWS_COMPLIMENT; i++ ) {
|
||||||
|
myRand = gen_random_int ( 1, 100 );
|
||||||
|
if ( myRand <= probFatal ) fatalities++;
|
||||||
|
else if ( myRand <= probInjury ) state->injured++;
|
||||||
|
}
|
||||||
|
state->uninjured = CREWS_COMPLIMENT - fatalities - state->injured;
|
||||||
|
}
|
||||||
|
// 4) Special conditions: hero
|
||||||
|
if (!allDone) {
|
||||||
|
if ( (shipsHealth>=8) && ( state->fuel_score <= FUEL_SCORE_FANTASTIC ) ) {
|
||||||
|
state->hero_counter++;
|
||||||
|
if ( state->hero_counter==1 ) sprintf ( buf, "HErO " );
|
||||||
|
else if ( state->hero_counter == LEVEL_ACE ) sprintf ( buf, " ACE " );
|
||||||
|
else if ( state->hero_counter == LEVEL_STARBUCK ) sprintf ( buf, "STrbUC" );
|
||||||
|
else if ( state->hero_counter>99 ) sprintf ( buf, "HEr%3d", state->hero_counter );
|
||||||
|
else sprintf ( buf, "HErO%2d", state->hero_counter ); // Typical case
|
||||||
|
allDone = true;
|
||||||
|
// Two rule sets for finding Earth. Alternate between easy and hard.
|
||||||
|
int8_t my_odds, temp;
|
||||||
|
if ( state->legend_counter %2 == 0 ) my_odds = (int8_t) state->hero_counter - LEVEL_STARBUCK; // Easy
|
||||||
|
else {
|
||||||
|
temp = ( state->hero_counter - LEVEL_STARBUCK ) + HARD_EARTH_INCREMENTS - 1;
|
||||||
|
my_odds = temp / HARD_EARTH_INCREMENTS;
|
||||||
|
my_odds = min ( my_odds, MAX_HARD_EARTH_CHANCE );
|
||||||
|
}
|
||||||
|
// Display odds in weekday region if positive value
|
||||||
|
if ( my_odds > 0 ) {
|
||||||
|
char buff3 [ 5 ];
|
||||||
|
sprintf ( buff3, "%2d", my_odds );
|
||||||
|
watch_display_text( WATCH_POSITION_TOP_RIGHT, buff3 );
|
||||||
|
} else watch_display_text( WATCH_POSITION_TOP_RIGHT, " " );
|
||||||
|
if ( my_odds >= gen_random_int ( 1, 200 ) ) { // EARTH!!!! The final objective.
|
||||||
|
sprintf ( buf, "EArTH " ); // 17% within 8, 50% by 16, 79% by 24, 94% by 32 <- easy mode
|
||||||
|
state->hero_counter = 0;
|
||||||
|
state->legend_counter++;
|
||||||
|
}
|
||||||
|
// Recalculate difficulty level base on new hero_counter.
|
||||||
|
state->difficulty_level = state->hero_counter / PROMOTION_INTERVAL;
|
||||||
|
state->difficulty_level = min ( state->difficulty_level, DIFFICULTY_LEVELS - 1 ); // Upper limit
|
||||||
|
// Write to EEPROM
|
||||||
|
write_to_lander_EEPROM(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 5) Set fuel conservation indicators as appropriate
|
||||||
|
if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_FANTASTIC ) ) watch_set_indicator ( WATCH_INDICATOR_LAP );
|
||||||
|
if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_GREAT ) ) watch_set_indicator ( WATCH_INDICATOR_24H );
|
||||||
|
if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_GOOD ) ) watch_set_indicator ( WATCH_INDICATOR_PM );
|
||||||
|
// 6) Set coffee maker OK indicator as appropriate
|
||||||
|
if ( shipsHealth >= 5 || ( shipsHealth >= 0 && ( gen_random_int ( 0, 3 ) != 1 ) ) ){
|
||||||
|
watch_set_indicator ( WATCH_INDICATOR_SIGNAL );
|
||||||
|
}
|
||||||
|
// 7) Green light if ship intact
|
||||||
|
if ( shipsHealth >= 8 && state->led_enabled) {
|
||||||
|
watch_set_led_green ( );
|
||||||
|
state->led_active = true;
|
||||||
|
}
|
||||||
|
// 8) Set standard display if not preempted.
|
||||||
|
if (!allDone) {
|
||||||
|
if ( ( state->injured > 0 ) || ( state->uninjured == 0 ) ) {
|
||||||
|
sprintf ( buf, "%d %2d%2d", shipsHealth, state->uninjured, state->injured );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sprintf ( buf, "%d %2d ", shipsHealth, state->uninjured );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Display final status.
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, buf );
|
||||||
|
} // End if tick_counter == 1
|
||||||
|
|
||||||
|
// Major crash - ship burning with red LED.
|
||||||
|
if ( state->ships_health < 0 && state->led_enabled) {
|
||||||
|
if ( ( gen_random_int ( 0, 1 ) != 1 ) && !last_pass ) { // Always off on last pass
|
||||||
|
// Turn on red LED.
|
||||||
|
watch_set_led_red ( );
|
||||||
|
state->led_active = true;
|
||||||
|
} else {
|
||||||
|
watch_set_led_off ( );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait long enough, then allow waiting for next game.
|
||||||
|
if ( last_pass ) {
|
||||||
|
watch_set_led_off ( );
|
||||||
|
// No change to display text, allow new game to start.
|
||||||
|
state->mode = MODE_WAITING_TO_START;
|
||||||
|
// Unless it's time for monsters
|
||||||
|
uint8_t survivors = state->injured + state->uninjured;
|
||||||
|
if ( ( state->ships_health >= 0 ) && ( survivors > 0 ) &&
|
||||||
|
( gen_random_int ( -1, 3 ) >= state->ships_health ) ) {
|
||||||
|
state->mode = MODE_MONSTER;
|
||||||
|
state->tick_counter = 0;
|
||||||
|
state->monster_type = gen_random_int ( 0, MONSTER_TYPES - 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // End if MODE_DISPLAY_FINAL_STATUS
|
||||||
|
else if ( state->mode == MODE_DISPLAY_SKILL_LEVEL ) {
|
||||||
|
// Display skill level
|
||||||
|
if ( state->tick_counter == 1 ) {
|
||||||
|
sprintf ( buf, " %d", state->skill_level );
|
||||||
|
watch_display_text ( WATCH_POSITION_TOP_RIGHT, buf );
|
||||||
|
sprintf ( buf, " %d ", state->skill_level );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf );
|
||||||
|
}
|
||||||
|
// Wait long enough, then start game.
|
||||||
|
if ( state->tick_counter >= ( 2.0 * LANDER_TICK_FREQUENCY ) ) {
|
||||||
|
state->tick_counter = 0;
|
||||||
|
// Houston, WE ARE LAUNCHING NOW....
|
||||||
|
state->mode = MODE_PLAYING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( state->mode == MODE_FIND_EARTH_MESSAGE ) {
|
||||||
|
// Display "Find" then "Earth"
|
||||||
|
if ( state->tick_counter == 1 ) {
|
||||||
|
sprintf ( buf, " FInd " );
|
||||||
|
watch_display_text ( WATCH_POSITION_TOP_RIGHT, " " );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf );
|
||||||
|
}
|
||||||
|
if ( state->tick_counter == (int) ( 1.5 * LANDER_TICK_FREQUENCY + 1 ) ) {
|
||||||
|
sprintf ( buf, "EArTH " );
|
||||||
|
watch_display_text ( WATCH_POSITION_TOP_RIGHT, " " );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf );
|
||||||
|
}
|
||||||
|
// Wait long enough, then display skill level.
|
||||||
|
if ( state->tick_counter >= ( 3 * LANDER_TICK_FREQUENCY ) ) {
|
||||||
|
state->tick_counter = 0;
|
||||||
|
state->mode = MODE_DISPLAY_SKILL_LEVEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( state->mode == MODE_MONSTER ) {
|
||||||
|
if ( state->tick_counter == 1 ) watch_display_text ( WATCH_POSITION_BOTTOM, lander_monster_names[state->monster_type] );
|
||||||
|
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS + 1 ) {
|
||||||
|
uint8_t my_rand;
|
||||||
|
my_rand = gen_random_int ( 0 , MONSTER_ACTIONS - 1 );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, lander_monster_actions[my_rand] );
|
||||||
|
}
|
||||||
|
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 ) { // Display 1st monster character
|
||||||
|
sprintf ( buf, "%s", lander_monster_names[state->monster_type] );
|
||||||
|
buf [1] = 0;
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
|
}
|
||||||
|
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 1 ) { // Display current population, close mouth
|
||||||
|
sprintf ( buf, " c%2d%2d", state->uninjured, state->injured );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf );
|
||||||
|
}
|
||||||
|
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 3 ) watch_display_character ( 'C', 5 ); // Open mouth
|
||||||
|
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 5 ) {
|
||||||
|
// Decision to: continue loop, end loop or eat astronaut
|
||||||
|
uint8_t survivors = state->injured + state->uninjured;
|
||||||
|
uint8_t myRand = gen_random_int ( 0, 16 );
|
||||||
|
if ( survivors == 0 ) state->mode = MODE_WAITING_TO_START;
|
||||||
|
else if ( myRand <= 1 ) { // Leave loop with survivors
|
||||||
|
sprintf ( buf, "%d %2d%2d", state->ships_health, state->uninjured, state->injured );
|
||||||
|
watch_display_text ( WATCH_POSITION_BOTTOM, buf);
|
||||||
|
state->mode = MODE_WAITING_TO_START;
|
||||||
|
} else if ( myRand <= 11 ) state->tick_counter = MONSTER_DISPLAY_TICKS * 2; // Do nothing, loop continues
|
||||||
|
else { // Eat an astronaut - welcome to the space program!
|
||||||
|
if ( state->injured > 0 && state->uninjured > 0 ) {
|
||||||
|
if ( gen_random_int ( 0,1 ) == 0 ) state->injured--;
|
||||||
|
else state->uninjured--;
|
||||||
|
}
|
||||||
|
else if ( state->injured > 0 ) state->injured--;
|
||||||
|
else state->uninjured--;
|
||||||
|
state->tick_counter = MONSTER_DISPLAY_TICKS * 2; // Re-display
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( state->tick_counter >= MONSTER_DISPLAY_TICKS * 4 ) state->mode = MODE_WAITING_TO_START; // Safety
|
||||||
|
} // End if MODE_MONSTER
|
||||||
|
break; // End case EVENT_TICK
|
||||||
|
case EVENT_ALARM_BUTTON_DOWN:
|
||||||
|
if ( state->mode == MODE_WAITING_TO_START ) {
|
||||||
|
// That was the go signal - start a new game!!
|
||||||
|
float numerator, denominator, timeSquared;
|
||||||
|
int16_t gravity, thrust;
|
||||||
|
float myTime, distToTop, fuel_mult;
|
||||||
|
uint8_t skill_level;
|
||||||
|
int32_t tplTop; // Top lander height for TPL calculations
|
||||||
|
movement_request_tick_frequency(LANDER_TICK_FREQUENCY);
|
||||||
|
watch_set_led_off ( ); // Safety
|
||||||
|
watch_clear_all_indicators ( );
|
||||||
|
// Randomize starting parameters
|
||||||
|
state->height = gen_random_int ( 131, 181 ) * 80;
|
||||||
|
// Per line below; see Mars Orbiter September 23, 1999
|
||||||
|
if ( gen_random_int ( 0, 8 ) == 5 ) state->height = gen_random_int ( 240, 800 ) * 80;
|
||||||
|
state->speed = gen_random_int ( -120, 35 ); // Positive is up
|
||||||
|
state->gravity = gen_random_int ( -3, -2 ) * 2; // negative downwards value
|
||||||
|
skill_level = gen_random_int ( 1, 4 ); // Precursor to fuel allocation
|
||||||
|
// Theoretical Perfect Landing (TPL) calculations start here.
|
||||||
|
myTime = (float) state->speed / (float) state->gravity; // How long to reach this speed? Don't care which way sign is.
|
||||||
|
distToTop = fabs ( 0.5 * state->gravity * myTime * myTime );
|
||||||
|
tplTop = (int) ( state->height + distToTop + 0.5 ); // Theoretical highest point based on all of speed, height and gravity.
|
||||||
|
// Time squared = ( 2 * grav * height ) / ( t*t + g*t ), where t is net acceleration with thrust on.
|
||||||
|
gravity = abs ( state->gravity );
|
||||||
|
thrust = ENGINE_THRUST + state->gravity;
|
||||||
|
numerator = 2.0 * (float) gravity * (float) tplTop;
|
||||||
|
denominator = thrust * thrust + thrust * gravity;
|
||||||
|
timeSquared = numerator / denominator;
|
||||||
|
state->fuel_tpl = (int) ( sqrt ( timeSquared ) + 0.5 ); // Fuel required for theoretical perfect landing (TPL).
|
||||||
|
if ( skill_level == 1 ) fuel_mult = 4.0; // TPL + 300%
|
||||||
|
else if ( skill_level == 2 ) fuel_mult = 2.5; // TPL + 150%
|
||||||
|
else if ( skill_level == 3 ) fuel_mult = 1.6; // TPL + 60%
|
||||||
|
else fuel_mult = 1.3; // TPL + 30%
|
||||||
|
state->fuel_start = state->fuel_tpl * fuel_mult;
|
||||||
|
state->fuel_remaining = state->fuel_start;
|
||||||
|
state->skill_level = skill_level;
|
||||||
|
state->tick_counter = 0;
|
||||||
|
if ( gen_random_int ( 1, 109 ) != 37 ) {
|
||||||
|
// Houston, approaching launch....
|
||||||
|
state->mode = MODE_DISPLAY_SKILL_LEVEL;
|
||||||
|
}
|
||||||
|
else state->mode = MODE_FIND_EARTH_MESSAGE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_BUTTON_DOWN:
|
||||||
|
if ( state->mode == MODE_WAITING_TO_START ) {
|
||||||
|
// Display difficulty level
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, lander_difficulty_names [state->difficulty_level]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_LONG_PRESS:
|
||||||
|
if ( state->mode != MODE_WAITING_TO_START ) break;
|
||||||
|
state->led_enabled = !state->led_enabled;
|
||||||
|
if (state->led_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_LONG_UP:
|
||||||
|
if ( ( state->mode == MODE_WAITING_TO_START ) && ( state->legend_counter > 0 ) ) {
|
||||||
|
if ( state->legend_counter > 9 ) sprintf (buf,"EArt%2d", state->legend_counter );
|
||||||
|
else sprintf (buf,"EArth%d", state->legend_counter );
|
||||||
|
// Display legend counter
|
||||||
|
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
movement_default_loop_handler(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ( !state->led_active ) return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lander_face_resign(void *context) {
|
||||||
|
(void) context;
|
||||||
|
watch_set_led_off ( );
|
||||||
|
}
|
||||||
152
watch-faces/complication/lander_face.h
Normal file
152
watch-faces/complication/lander_face.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Klingon Jane
|
||||||
|
*
|
||||||
|
* 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 LANDER_FACE_H_
|
||||||
|
#define LANDER_FACE_H_
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
My remake of a classic planet landing game.
|
||||||
|
|
||||||
|
Objective: Safely land the Cringeworthy.
|
||||||
|
Use your limited fuel supply to achieve a soft touch-down.
|
||||||
|
|
||||||
|
End scenarios and ship's health:
|
||||||
|
|
||||||
|
Hero They name this planet after you.
|
||||||
|
8 Life is very cozy.
|
||||||
|
7
|
||||||
|
6
|
||||||
|
5 Life is tolerable, plus some creature comforts
|
||||||
|
4
|
||||||
|
3 Marooned.
|
||||||
|
2
|
||||||
|
1
|
||||||
|
0 Ship destroyed. Life is harsh, no shelter. Giant hamsters are cute. **
|
||||||
|
Bug As in squished.
|
||||||
|
Crater They name this crater after you.
|
||||||
|
Gone As in vapourized.
|
||||||
|
|
||||||
|
Landing display format is:
|
||||||
|
Ship's health, intact crewmen, injured crewmen.
|
||||||
|
|
||||||
|
Additional data:
|
||||||
|
Crew's compliment: 13.
|
||||||
|
Low fuel warning icons: activates when 3 seconds of full thrust remains.
|
||||||
|
** Yes, hamsters are very cute. However; some eating of astronauts may occur.
|
||||||
|
|
||||||
|
Starting velocity, height and gravity are randomized each scenario.
|
||||||
|
Fuel levels randomly assigned from 1 to 4 (hardest) to match starting parameters.
|
||||||
|
|
||||||
|
A safe landing is always possible.
|
||||||
|
|
||||||
|
End of game icons:
|
||||||
|
LAP - Fantastic budgeting of fuel supply ( Required for heroic landing status. )
|
||||||
|
24H - Great budgeting of fuel supply
|
||||||
|
PM - Good budgeting of fuel supply
|
||||||
|
SIGNAL - The combination coffee and tea maker survived
|
||||||
|
|
||||||
|
Landings get progressively harder with the number of heroic landings made.
|
||||||
|
Number of heroic landings are remembered.
|
||||||
|
|
||||||
|
Heroic
|
||||||
|
Landings Status
|
||||||
|
0 Normal
|
||||||
|
3 Hard ( first difficulty increase )
|
||||||
|
6 Harder ( final difficulty increase )
|
||||||
|
8 Ace
|
||||||
|
11 ??????
|
||||||
|
|
||||||
|
Save yourself. Save the coffee maker.
|
||||||
|
|
||||||
|
END of standard training manual
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
What is really going on here?
|
||||||
|
The fleet is lost. You are a newbie pilot making a name for yourself.
|
||||||
|
|
||||||
|
Objective: Find Earth.
|
||||||
|
|
||||||
|
After reaching ?????? status, future heroic sorties will have 'some' chance in 200
|
||||||
|
of finding Earth.
|
||||||
|
|
||||||
|
Your chances improve by 1 chance in 200 for each subsequent Heroic Landing (HL).
|
||||||
|
|
||||||
|
Completing HL 12 will give you 1 chance in 200, for that landing.
|
||||||
|
HL 13 will give you 2 chances in 200, for that landing.
|
||||||
|
HL 14 will give you 3 chances in 200, for that landing.
|
||||||
|
HL 20 will give you 9 chances in 200, for that landing, and so on.
|
||||||
|
|
||||||
|
At these higher levels, your chances in 200 are displayed in the upper right corner on a heroic landing.
|
||||||
|
|
||||||
|
For wannabe pilots only: The HL counter can be reset by crashing three consecutive
|
||||||
|
missions without touching the thrust button. ( 6 to reset Earth-found counter )
|
||||||
|
|
||||||
|
Find Earth. Save Humanity.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t height;
|
||||||
|
int16_t speed; // Positive is up
|
||||||
|
uint16_t tick_counter; // For minimum delays
|
||||||
|
uint16_t fuel_start;
|
||||||
|
uint16_t fuel_remaining;
|
||||||
|
uint16_t fuel_tpl; // Fuel required for theoretical perfect landing
|
||||||
|
uint16_t fuel_score; // 100 is perfect; higher is less perfect
|
||||||
|
int8_t gravity; // negative downwards value
|
||||||
|
bool led_enabled; // Can the led be turned on?
|
||||||
|
bool led_active; // Did we use it this scenario?
|
||||||
|
uint8_t mode; // 0 Pre-launch waiting, 1 show level, 2 playing, 3 touchdown blank, 4 final display, 5 monster
|
||||||
|
uint8_t skill_level; // 1 thru 4. Dictates fuel alloted
|
||||||
|
int8_t ships_health; // 0 thru 8. -1 = major crash
|
||||||
|
uint8_t hero_counter; // Total heroic landings ever
|
||||||
|
uint8_t legend_counter; // Historic events counter ( Earth )
|
||||||
|
uint8_t difficulty_level; // Based on hero_counter
|
||||||
|
uint8_t reset_counter; // Can reset hero_counter by crashing using zero fuel several consecutive scenarios
|
||||||
|
uint8_t monster_type; // Which monster is hungry?
|
||||||
|
uint8_t uninjured; // OK survivors
|
||||||
|
uint8_t injured; // Hurt survivors
|
||||||
|
} lander_state_t;
|
||||||
|
|
||||||
|
void lander_face_setup(uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void lander_face_activate(void *context);
|
||||||
|
bool lander_face_loop(movement_event_t event, void *context);
|
||||||
|
void lander_face_resign(void *context);
|
||||||
|
|
||||||
|
#define lander_face ((const watch_face_t){ \
|
||||||
|
lander_face_setup, \
|
||||||
|
lander_face_activate, \
|
||||||
|
lander_face_loop, \
|
||||||
|
lander_face_resign, \
|
||||||
|
NULL, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // LANDER_FACE_H_
|
||||||
@ -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