Added lander face
This commit is contained in:
parent
86fd87a326
commit
9fbaadeaac
@ -73,4 +73,5 @@
|
||||
#include "wareki_face.h"
|
||||
#include "deadline_face.h"
|
||||
#include "wordle_face.h"
|
||||
#include "lander_face.h"
|
||||
// New includes go above this line.
|
||||
|
||||
@ -48,4 +48,5 @@ SRCS += \
|
||||
./watch-faces/sensor/lis2dw_monitor_face.c \
|
||||
./watch-faces/complication/wareki_face.c \
|
||||
./watch-faces/complication/deadline_face.c \
|
||||
./watch-faces/complication/lander_face.c \
|
||||
# New watch faces go above this line.
|
||||
|
||||
572
watch-faces/complication/lander_face.c
Normal file
572
watch-faces/complication/lander_face.c
Normal file
@ -0,0 +1,572 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#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_string("LA", 0);
|
||||
else watch_display_string("LE", 0);
|
||||
if ( ( state->hero_counter == 0 ) || ( state->hero_counter >= 40 ) ) watch_display_string ( " ", 2);
|
||||
else
|
||||
{
|
||||
sprintf ( buf, "%2d", state->hero_counter );
|
||||
watch_display_string(buf, 2);
|
||||
}
|
||||
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_string ( buf, 4);
|
||||
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_string ( "ESCAPE", 4 );
|
||||
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_string ( buf, 4 );
|
||||
}
|
||||
}
|
||||
else if ( state->mode == MODE_TOUCHDOWN_BLANK ) {
|
||||
// Blank display on touchdown
|
||||
if ( state->tick_counter == 1 ) {
|
||||
watch_clear_all_indicators ();
|
||||
watch_display_string ( " ", 4 );
|
||||
|
||||
// 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_string ( "rESET ", 4 );
|
||||
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_string ( buff3, 2 );
|
||||
} else watch_display_string ( " ", 2 );
|
||||
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_string ( buf, 4 );
|
||||
} // 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 %d ", state->skill_level, state->skill_level );
|
||||
watch_display_string ( buf, 2 );
|
||||
}
|
||||
// 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_string ( buf, 2 );
|
||||
}
|
||||
if ( state->tick_counter == (int) ( 1.5 * LANDER_TICK_FREQUENCY + 1 ) ) {
|
||||
sprintf ( buf, " EArTH " );
|
||||
watch_display_string ( buf, 2 );
|
||||
}
|
||||
// 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_string ( lander_monster_names[state->monster_type], 4 );
|
||||
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS + 1 ) {
|
||||
uint8_t my_rand;
|
||||
my_rand = gen_random_int ( 0 , MONSTER_ACTIONS - 1 );
|
||||
watch_display_string ( lander_monster_actions[my_rand], 4 );
|
||||
}
|
||||
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_string(buf,4);
|
||||
}
|
||||
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_string ( buf, 5 );
|
||||
}
|
||||
else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 3 ) watch_display_string ( "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_string ( buf, 4 );
|
||||
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_string ( lander_difficulty_names [state->difficulty_level], 4 );
|
||||
}
|
||||
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_string ( buf, 4 );
|
||||
}
|
||||
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_
|
||||
Loading…
x
Reference in New Issue
Block a user