Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions movement_faces.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,5 @@
#include "higher_lower_game_face.h"
#include "lander_face.h"
#include "simon_face.h"
#include "tcg_life_counter_face.h"
// New includes go above this line.
1 change: 1 addition & 0 deletions watch-faces.mk
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ SRCS += \
./watch-faces/complication/higher_lower_game_face.c \
./watch-faces/complication/lander_face.c \
./watch-faces/complication/simon_face.c \
./watch-faces/complication/tcg_life_counter_face.c \
# New watch faces go above this line.
178 changes: 178 additions & 0 deletions watch-faces/complication/tcg_life_counter_face.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* MIT License
*
* Copyright (c) 2025 Mark Schlosser
*
* 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.
*/

#include <stdlib.h>
#include <string.h>
#include "tcg_life_counter_face.h"

#define TCG_LIFE_COUNTER_NUM_LIFE_VALUES 2

static const int16_t _tcg_life_counter_defaults[] = {
20,
40
};
#define TCG_LIFE_COUNTER_DEFAULT_SIZE() (sizeof(_tcg_life_counter_defaults) / sizeof(int16_t))

static const int8_t _tcg_life_counter_increment_amts[] = {
1,
5
};
#define TCG_LIFE_COUNTER_INCREMENT_AMTS_SIZE() (sizeof(_tcg_life_counter_increment_amts) / sizeof(int8_t))

void tcg_life_counter_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(tcg_life_counter_state_t));
memset(*context_ptr, 0, sizeof(tcg_life_counter_state_t));
tcg_life_counter_state_t *state = (tcg_life_counter_state_t *)*context_ptr;
state->default_idx = 0;
for (size_t i = 0; i < TCG_LIFE_COUNTER_NUM_LIFE_VALUES; i++)
state->life_values[i] = _tcg_life_counter_defaults[state->default_idx];
state->increment_mode_on = false;
state->increment_idx = 0;
}
}

void tcg_life_counter_face_activate(void *context) {
tcg_life_counter_state_t *state = (tcg_life_counter_state_t *)context;
}

static bool _tcg_life_counter_is_initial_default_values(tcg_life_counter_state_t *state) {
return (_tcg_life_counter_defaults[state->default_idx] == state->life_values[0] &&
_tcg_life_counter_defaults[state->default_idx] == state->life_values[1] &&
0 == state->increment_idx &&
false == state->increment_mode_on);
}

bool tcg_life_counter_face_loop(movement_event_t event, void *context) {

tcg_life_counter_state_t *state = (tcg_life_counter_state_t *)context;

switch (event.event_type) {
case EVENT_LIGHT_BUTTON_DOWN:
break;
case EVENT_LIGHT_BUTTON_UP:
if (!state->increment_mode_on) {
if (state->life_values[0] > 0) {
int16_t temp;
temp = (int16_t)state->life_values[0];
temp -= _tcg_life_counter_increment_amts[state->increment_idx]; // decrement counter index 0
if (temp < 0)
temp = 0;
state->life_values[0] = (uint16_t)temp;
}
} else {
if (state->life_values[0] < 999) {
state->life_values[0] += _tcg_life_counter_increment_amts[state->increment_idx]; // increment counter index 0
if (state->life_values[0] > 999)
state->life_values[0] = 999;
}
}
print_tcg_life_counter(state);
break;
case EVENT_LIGHT_LONG_PRESS:
#ifdef TCG_LIFE_COUNTER_FACE_ENABLE_LED
movement_illuminate_led();
#endif
state->increment_mode_on = !state->increment_mode_on;
print_tcg_life_counter(state);
break;
case EVENT_ALARM_BUTTON_DOWN:
break;
case EVENT_ALARM_BUTTON_UP:
if (!state->increment_mode_on) {
if (state->life_values[1] > 0) {
int16_t temp;
temp = (int16_t)state->life_values[1];
temp -= _tcg_life_counter_increment_amts[state->increment_idx]; // decrement counter index 0
if (temp < 0)
temp = 0;
state->life_values[1] = (uint16_t)temp;
}
} else {
if (state->life_values[1] < 999) {
state->life_values[1] += _tcg_life_counter_increment_amts[state->increment_idx]; // increment counter index 1
if (state->life_values[1] > 999)
state->life_values[1] = 999;
}
}
print_tcg_life_counter(state);
break;
case EVENT_ALARM_LONG_PRESS:
// possibly advance to next set of default values
if (_tcg_life_counter_is_initial_default_values(state)) {
state->default_idx++;
if (state->default_idx >= TCG_LIFE_COUNTER_DEFAULT_SIZE())
state->default_idx = 0;
// reset all life counters
for (size_t i = 0; i < TCG_LIFE_COUNTER_NUM_LIFE_VALUES; i++)
state->life_values[i] = _tcg_life_counter_defaults[state->default_idx];
state->increment_mode_on = false;
state->increment_idx = 0;
} else {
state->increment_idx++;
if (state->increment_idx >= TCG_LIFE_COUNTER_INCREMENT_AMTS_SIZE())
state->increment_idx = 0;
}
print_tcg_life_counter(state);
break;
case EVENT_MODE_LONG_PRESS:
if (_tcg_life_counter_is_initial_default_values(state)) {
movement_move_to_face(0);
} else {
// reset all life counters
for (size_t i = 0; i < TCG_LIFE_COUNTER_NUM_LIFE_VALUES; i++)
state->life_values[i] = _tcg_life_counter_defaults[state->default_idx];
state->increment_mode_on = false;
state->increment_idx = 0;
print_tcg_life_counter(state);
}
break;
case EVENT_ACTIVATE:
print_tcg_life_counter(state);
break;
case EVENT_TIMEOUT:
// ignore timeout
break;
default:
movement_default_loop_handler(event);
break;
}

return true;
}

void print_tcg_life_counter(tcg_life_counter_state_t *state) {
char buf[7];
char buf2[3];
watch_display_text(WATCH_POSITION_TOP, "TC");
sprintf(buf2, state->increment_mode_on ? "i%1d" : "d%1d", _tcg_life_counter_increment_amts[state->increment_idx]);
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf2);
sprintf(buf, "%3d%3d", state->life_values[0], state->life_values[1]);
watch_display_text(WATCH_POSITION_BOTTOM, buf);
}

void tcg_life_counter_face_resign(void *context) {
(void) context;
}
69 changes: 69 additions & 0 deletions watch-faces/complication/tcg_life_counter_face.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* MIT License
*
* Copyright (c) 2025 Mark Schlosser
*
* 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 TCG_LIFE_COUNTER_FACE_H_
#define TCG_LIFE_COUNTER_FACE_H_

/*
* TCG_LIFE_COUNTER face
*
* TCG Life Counter face is designed to track player life totals in a two-player trading card game. Two life totals will be displayed on the screen. The left counter is controlled by short-pressing LIGHT. The right counter is controlled by short-pressing ALARM.
* Life counters each begin at `20` and the face begins in decrement mode with a decrement value of `1`. This means the associated player's life total will decrement by a value of `1` each time LIGHT or ALARM is pressed. Once the face is changed to increment mode, the associated player's life total will increase by `1` each time LIGHT or ALARM is pressed.
*
* Usage:
* Short-press LIGHT to decrement or increment (determined by mode) left counter (player 1). Clamps to `0`-`999`.
* Short-press ALARM to decrement or increment (determined by mode) right counter (player 2). Clamps to `0`-`999`.
* Long-press LIGHT to toggle mode to decrement or increment mode, indicated by a `d` or `i` character in the top right of LCD. The number `1` or `5` will be visible next to this character, indicating the current increment/decrement amount.
* Long-press MODE to reset TCG life counter to decrement mode, decrement amount to `1`, and both life counters to the current initial value. If the face is displaying initial values, this action will instead return to the watch's first face.
* Long-press ALARM to advance to the next set of increment/decrement values (`1` and `5`). The initial increment/decrement value is configured to `1`. If the face is displaying initial values, this action will instead advance to the next set of initial life values (`20` and `40`). The face starts with an initial life value of `20`.
*/

#include "movement.h"

// Uncomment the following to enable the LED lighting upon holding LIGHT.
// #define TCG_LIFE_COUNTER_FACE_ENABLE_LED

typedef struct {
uint16_t life_values[2];
bool increment_mode_on;
uint8_t default_idx;
uint8_t increment_idx;
} tcg_life_counter_state_t;

void tcg_life_counter_face_setup(uint8_t watch_face_index, void ** context_ptr);
void tcg_life_counter_face_activate(void *context);
bool tcg_life_counter_face_loop(movement_event_t event, void *context);
void tcg_life_counter_face_resign(void *context);

void print_tcg_life_counter(tcg_life_counter_state_t *state);

#define tcg_life_counter_face ((const watch_face_t){ \
tcg_life_counter_face_setup, \
tcg_life_counter_face_activate, \
tcg_life_counter_face_loop, \
tcg_life_counter_face_resign, \
NULL, \
})

#endif // TCG_LIFE_COUNTER_FACE_H_