simple ASCII-based game


Assignment One
Weight: 15% of the unit
1 Introduction
Your task for this assignment is to design, code and test a game inspired from a classical puzzle
game “Laser Tank” using the C programming language (in the C89 Standard). In summary, your
program will:
• Read the parameters from command line and utilize them for the game configuration.
• Create dynamically-allocated 2D char array to make a simple ASCII-based game.
• Receive user input to control the flow of the game.
• Write a suitable makefile. Without the makefile, we will assume it cannot be compiled.
2 Code Design
You must thoroughly document your code using C comments (/* ... */). For each function you
define and each datatype you declare (e.g. using struct or typedef), place a comment
immediately above it explaining its purpose, how it works, and how it relates to other functions
and types. Collectively, these comments should explain your design.
Your code should be structured in a way that each file has a clear scope and goal. For example,
“main.c” should only contain a main function, “game.c” should contain functions that handle
and control the game features, and so on. Functions should be located in reasonably appropriate
files and you will link them using the makefile. DO NOT put everything together into one single
source code file. Make sure you use the header files and header guards correctly. Never include
.c files directly.
Make sure you free all the memory allocated by the malloc() function. Use valgrind to detect
any memory leak and fix them. Memory leaks might not break your program, but penalty will
still be applied if there is any. If you do not use malloc, you will lose marks.
Keep in mind that it is possible to lose significant marks with a fully working program that
is written messily, has poor structure, contains memory leaks, or violates many of the
coding standards.
Curtin College IT
Adapted from Curtin University — Discipline of Computing
Unix & C Programming (UCP1000)
Trimester 1, 2024
UCP Assignment One
2
3 Academic Integrity
This is an assessable task, and as such there are strict rules. You must not ask for or accept help
from anyone else on completing the tasks. You must not show your work at any time to
another student enrolled in this unit who might gain unfair advantage from it. These things
are considered plagiarism or collusion. To be on the safe side, never ever release your code to
the public.
Do NOT copy C source code from internet resources or artificial intelligence code generators.
Copying code from the internet without referencing will be considered plagiarism. If you
include code from other sources with a valid reference, then that part of the code will not be
marked since it is not your work.
Staff can help in understanding the unit material in general, but nobody is allowed to help you
solve the specific problems posed in this specification. The purpose of the assignment is for
you to solve them on your own, so that you learn from it. Please see Curtin College’s
Academic Integrity policy for information on academic misconduct (which includes
plagiarism and collusion).
You will be required to attend quick interview to answer questions about your program. In
order for your work to be graded you will be required to explain how it works. Any code that
you cannot adequately explain to your tutor will not receive marks and may be referred to as
evidence in an Academic Misconduct inquiry.
4 Task Details
4.1 Quick Preview
You will implement a simple game inspired from a classical puzzle game “Laser Tank”.
Further details on the specification will be explained on the subsequent sections.
4.2 Command Line Arguments
Please ensure that the executable is called “laserTank”. Your executable should accept eight
(8) command-line parameters/arguments:
laserTank <row_size> <col_size> <player_row> <player_col>
<player_direction> <enemy_row> <enemy_col> <enemy_direction>
• <row_size> and <col_size> determines the size of the playable map. The minimum and
maximum value for <row_size> and <col_size> are 5 and 25 respectively. You need these
numbers for dynamic memory allocations of 2D char array. (use malloc() function)
• <player_row> and <player_col> are the coordinates of the player tank. You can use these
numbers as the array index of the map. Coordinate <0,0> is located at top left of the map.
• <player_direction> is the direction the player is facing upon starting the game. There are 4
choices: n,s,e,w (lowercase letter) which is derived from the words north, south, east and west.
• <enemy_row>, <enemy_col>, and <enemy_direction> have the same meaning as the previous
UCP Assignment One
3
explanations. However, these arguments configure the enemy tank.
This is one example: ./laserTank 10 20 1 1 w 5 10 n
2.1 Map
The map can be derived from a 2D char array, which has been dynamically allocated based on
the command line arguments.
2.2 Main Interface
Once you run the program, it should clear the terminal screen and print the map along with the
instructions:
This is the interface where the user plays the game. The user can type the command after the
sentence “action:”. Every time the user inputs the command, the program should update the
map accordingly and refresh the screen. (clear the screen and reprint everything)
2.3 User Input
The user only need to type 1 character for each command (lower case). Here is the list of the
possible commands:
• ‘w’ moves the player one block above. If the player tank is not facing upwards, this
command will only cause the tank to face upwards.‘s’ moves the player one block below.
If the player tank is not facing downwards, this command will only cause the tank to face
downwards.‘a’ moves the player one block to the left. If the player tank is not facing
leftwards, this command will only cause the tank to face leftwards.
• ‘d’ moves the player one block to the right. If the player tank is not facing rightwards,
this command will only cause the tank to face rightwards.
• ‘f’ causes the player tank to shoot the laser on the direction it is facing. Please refer to
the next section on ”Laser Animation” for more details. Additionally, please watch the
supplementary video on “Assignment Demonstration”.
• Any other character will be ignored. The program still need to refresh the interface to
Note: Remember, the name of the executable is stored in variable argv[0].
UCP Assignment One
4
enter new command. You can add warning message if you want.
2.4 Laser Animation
When the user provides the command ’f’, there will be an additional character representing the
laser on the map. You can use the character ’-’ if the tank is facing horizontal direction, or the
character ’|’ if the tank is facing the vertical direction. When the laser is being shot, the program
should trigger a simple animation of the laser moving from the front of the player tank. The
laser will keep moving forward until it hits the border OR the enemy tank. When the laser hits
the enemy tank, its character should be replaced with ’X’.
In order to make a simple animation, you can use the UCPSleep function provided in Moodle.
The idea is to move the position of the laser one block at a time with a very brief sleeping period
in between. We recommend sleeping time less than 0.5 seconds for reasonable speed. When
the animation is still running, the program should NOT show the game instructions and NOT
prompt the user for any command.
The laser should be printed on the map using a font color other than the default color.
2.5 Winning/Losing Conditions
The game will continue until the player either win or lose. This is the condition for each result:
• WIN: The player tank manage to hit the enemy tank with the laser. This can only happen when the player tank shoots the laser while facing the enemy tank, regardless of the
distance between them. The enemy tank’s character will change into a character ‘X’
(uppercase X).
• LOSE: The player moves in front of the enemy tank (facing the player). The enemy tank
will immediately shoots the laser to the player (with the laser animation). The player
cannot move when the animation is happening. The player’s character will change into
a character ‘X’.
Note: If the player attempts to move to the same location as the map border OR the
enemy tank, it should not do anything. (player location stays the same)
Note: If the enemy tank is exactly in front of the player (no distance), then there is no
need to show the laser, and the enemy tank should get destroyed immediately and the
game should conclude.
UCP Assignment One
5
When this occurs, please do not forget to free all the memories allocated via malloc() function
before the program ends.
2.6 Makefile
You should write the makefile according to the format explained in the lecture and practical. It
should include the Make variables, appropriate flags, appropriate targets, and clean rule. We
will compile your program through the makefile. If the makefile is missing, we will assume the
program cannot be compiled, and you will lose marks.
2.7 Assumptions
For simplification purpose, these are assumptions you can use for this assignment:
• The coordinates of the player tank and enemy tank will not overlap from the provided
command line arguments. But, you still need to check whether the coordinates are inside
the map boundary.
• The coordinate of the tanks will not overlap with the border of the map as well.
• The player tank will NOT be in front of the enemy tank immediately upon starting the
game. (NO instant lose)
• Any command or argument is always on correct datatype and in lower case (for letters).
3 Marking Criteria
This is the marking distribution for your guidance:
Code Structure:
• Contains any NON-C89 syntax. Compile with -ansi and -pedantic flags to prevent losing
marks on this. (5 marks)
• Properly structured makefile (10 marks)
• Program can be compiled with the makefile and executed successfully without immediate crashing (5 marks)
UCP Assignment One
6
• Usage of sufficient in-code commenting (5 marks)
• The whole program is readable and has reasonable structure, Multiple files are utilized (5
marks).
• Avoiding bad coding standard. (10 marks)
Some of the most common mistakes are:
– Usingglobalvariables
– Callingexit()functiontoendtheprogramabruptly
– Using“break’‘notonthe switchcase statement
– Using“continue’‘
• No memory leaks (10 marks)
• Please use valgrind to check for any leaks. If your program is very sparse OR does not use
any malloc(), you will get zero mark on this category.
Functionality:
– Able to read and interpret command line arguments correctly. This includes clarifying the argument amount and values. If there is any issue, then the error message
is printed on terminal and end the program. Otherwise, the map array should be
allocated accordingly. (10 marks)
– Correct characters usage for the map and its components. (5 marks)
– Player tank moves and changes direction correctly based on player command. (10
marks)
– The laser is animated correctly on the map with the UCPSleep function. (5 marks)
– (BONUS mark) The laser is changing color every time it moves by one block. (hint:
use static variable to keep track.) (5 marks) (total mark is still capped at 100 marks.)
– Able to refresh the screen with the updated map/instructions visualization every
time the user enters the command. (5 marks)
– Winning/Losing condition is implemented correctly. (make sure to free the memory before wrapping up the program) (10 marks)
4 Short Report
If your program is incomplete/imperfect, please write a brief report explaining what you have
done on the assignment. This report is not marked, but it will help us a lot to mark your assignment. Please ensure your report reflects your work correctly (no exaggeration). Dishonest
report will lead to academic misconduct.
5 Final Check and Submission
After you complete your assignment, please make sure it can be compiled and run on a Linuxbased operating system. If you do your assignment on other environments (e.g on Windows
operating system), then it is your responsibility to ensure that it works in a linux
environment. Ubuntu will be used for testing. In the case of incompatibility, it will be considered
“not working’‘ and some penalties will apply. You have to submit a tarball containing:
• Your essential assignment files – Submit all the .c & .h files and your makefile. Please
UCP Assignment One
7
do not submit the executable and object (.o) files, as we will re-compile them anyway.
• Brief Report (if applicable) - If you want to write the brief report about what you have
done on the assignment, please save it as a PDF or TXT file.
The name of the tarball should be in the format of:
<student-ID>_<full-name>_Assignment1.tar.gz
Example: 12345678_Antoni-Liang_Assignment1.tar.gz
Please make sure your submission is complete and not corrupted. You can re-download the
submission and check if you can compile and run the program again. Corrupted submission
will receive instant zero. You can submit it multiple times, but only your latest submission will
WX:codinghelp

/* Full ESP32 program - extended from user's base. Features: - WS2812B LED strip (12 leds) - TTP229 16-key reading - Buzzer (LEDC) for tone output - SPI OLED (128x64) driver (no ssd1306) - Mode keys: physical TP4..TP7 as function keys - Mode4: LED-key interactive (latched while pressed, fade on release) - Mode5: LED gradient rotating - Mode6: Play blessing tune loop with LED feedback - Mode7: mini-game (7 regions) on OLED */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/gpio.h" #include "driver/ledc.h" #include "driver/spi_master.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "led_strip.h" #include <math.h> #include "esp_random.h" #define TAG "TTP229_16KEY_EXT" /// Hardware pins (adjust to your wiring) #define WS2812B_PIN 2 #define NUM_LEDS 12 #define TTP229_SCL 27 #define TTP229_SDO 26 #define BUZZER_PIN 25 // OLED SPI pins (example, change if different) #define OLED_SPI_HOST HSPI_HOST #define OLED_MISO_PIN -1 #define OLED_MOSI_PIN 17 #define OLED_SCLK_PIN 4 #define OLED_DC_PIN 18 #define OLED_CS_PIN 19 #define OLED_RST_PIN 5 #define OLED_WIDTH 128 #define OLED_HEIGHT 64 #define OLED_BUF_SIZE ((OLED_WIDTH * OLED_HEIGHT) / 8) #define M_PI 3.14159265358979323846 #pragma GCC diagnostic ignored "-Wunused-const-variable" static const char *TAG2 = "OLED"; led_strip_handle_t led_strip; /// Rainbow preset for LED colors (RGB) const uint8_t rainbow_colors[NUM_LEDS][3] = { {255,0,48}, {255,89,0}, {255,183,0}, {255,233,0}, {136,255,0}, {0,255,136}, {0,204,255}, {0,64,255}, {89,0,255}, {183,0,255}, {255,0,191}, {255,0,98} }; // 16-key note frequencies (C4~F5) static const int note_freqs[16] = { 262,294,330,349,370,392,415,440,466,494,523,554,587,622,659,698 }; // mapping logical index i -> TP number (as in your original) static const uint8_t key_map[16] = {3,2,1,0,15,14,13,12,8,9,10,11,4,5,6,7}; // forward declarations static void ws2812b_init(void); static void ttp229_init(void); static uint16_t ttp229_read_keys(void); static void buzzer_init(void); static void buzzer_play_tone(int freq); static void oled_init(void); static void oled_refresh(void); static void oled_clear(void); static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm); static void oled_draw_rect(int x,int y,int w,int h); static void oled_fill_rect(int x,int y,int w,int h); static void oled_draw_text_centered(const char *txt); static void oled_draw_music_bars(uint16_t keys); static void start_blessing_task(void); static void stop_blessing_task(void); static void blessing_led_feedback(int step); // Global state typedef enum { MODE_NORMAL = 0, MODE_LED_KEY_INTERACT, // TP4 MODE_LED_GRADIENT_CYCLE, // TP5 MODE_BLESSING_PLAY, // TP6 MODE_GAME // TP7 } app_mode_t; static volatile app_mode_t g_mode = MODE_NORMAL; // Variables for LED-key interactivity static uint8_t led_brightness[NUM_LEDS]; // 0-255 brightness static uint8_t led_active[NUM_LEDS]; // 1=latched on, 0=idle static SemaphoreHandle_t led_mutex = NULL; // Gradient cycle control static volatile bool gradient_run = false; static int gradient_pos = 0; // Blessing task handle static TaskHandle_t blessing_task_handle = NULL; static volatile bool blessing_running = false; // Game state static bool game_running = false; static uint8_t game_regions[7]; // 1=block present, 0=empty static TickType_t game_next_spawn; // OLED framebuffer static uint8_t oled_buf[OLED_BUF_SIZE]; static spi_device_handle_t spi_oled; // Simple bitmaps (8x8 or larger) for emoticons/music glyphs // Emoticon 1: (^_^) - 16x8 bitmap (example) static const uint8_t bm_face1[] = { // 16x8 (each byte is 8 vertical pixels column-major if we render simply) 0x00,0x08,0x04,0x02,0x04,0x08,0x40,0x40, 0x40,0x40,0x08,0x04,0x02,0x04,0x08,0x00 }; // Emoticon 2: (o_o) static const uint8_t bm_face2[] = { 0x00,0x0E,0x0A,0x0A,0x0E,0x40,0x20,0x10, 0x20,0x40,0x0E,0x0A,0x0A,0x0E,0x00,0x00 }; // Music symbol (simple 12x12) static const uint8_t bm_music[] = { 0x10,0x10,0x10,0x18,0x18,0x1C,0x1C,0x0E,0x06,0x06,0x00,0x00, 0x1F,0x1F,0x0F,0x0F,0x07,0x07,0x03,0x03,0x01,0x01,0x00,0x00 }; // Small filled block 12x12 for game tile static const uint8_t bm_block12[12*2] = { // we'll draw block by filling rectangle, not use bitmap }; // Blessing tune array (freq, duration_ms) - shortened example uses your full data typedef struct { int freq; int dur; } tone_t; static const tone_t blessing[] = { // I will include the start of your provided data; you can extend as necessary. {494,600},{600,600},{440,600},{440,600},{392,600},{392,600},{370,600},{392,600},{392,600},{494,600},{440,600}, // (NOTE: in your provided sheet you had many entries — paste the entire array here in your final code) // For brevity in this example, repeat a few to demonstrate: {440,600},{392,600},{392,600},{370,600},{392,600},{392,600}, {0,600},{0,600},{0,600} // zeros mean rest }; static const int blessing_len = sizeof(blessing)/sizeof(blessing[0]); /*** --- Implementation --- ***/ static void ws2812b_init(void){ led_strip_config_t strip_config = { .strip_gpio_num = WS2812B_PIN, .max_leds = NUM_LEDS, }; led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000, }; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config,&rmt_config,&led_strip)); // Initialize to rainbow for (int i = 0 ; i < NUM_LEDS ; i++){ led_strip_set_pixel(led_strip, i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2]); } led_strip_refresh(led_strip); if (!led_mutex) led_mutex = xSemaphoreCreateMutex(); // initialize brightness/active arrays for (int i=0;i<NUM_LEDS;i++){ led_brightness[i]=255; led_active[i]=0; } } static void ttp229_init(void) { gpio_config_t io_conf = {0}; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = 1ULL << TTP229_SCL; gpio_config(&io_conf); io_conf.mode = GPIO_MODE_INPUT; io_conf.pin_bit_mask = 1ULL << TTP229_SDO; gpio_config(&io_conf); gpio_set_level(TTP229_SCL, 1); vTaskDelay(pdMS_TO_TICKS(10)); } static uint16_t ttp229_read_keys(void) { uint16_t data = 0; gpio_set_level(TTP229_SCL, 1); esp_rom_delay_us(2); for (int i = 0; i < 16; i++) { gpio_set_level(TTP229_SCL, 0); esp_rom_delay_us(2); int bit = gpio_get_level(TTP229_SDO); if (bit == 0) data |= (1 << i); gpio_set_level(TTP229_SCL, 1); esp_rom_delay_us(2); } return data; } static void buzzer_init(void) { ledc_timer_config_t timer_conf = { .duty_resolution = LEDC_TIMER_10_BIT, .freq_hz = 1000, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER_0 }; ledc_timer_config(&timer_conf); ledc_channel_config_t channel_conf = { .channel = LEDC_CHANNEL_0, .duty = 0, .gpio_num = BUZZER_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_sel = LEDC_TIMER_0 }; ledc_channel_config(&channel_conf); } static void buzzer_play_tone(int freq) { if (freq <= 0) { ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); return; } ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq); ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 512); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); } /*** Simple SPI OLED (128x64) - basic frame buffer + commands ***/ static void oled_send_cmd(const uint8_t *data, size_t len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, .flags = 0 }; // DC low for command gpio_set_level(OLED_DC_PIN, 0); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t)); } static void oled_send_data(const uint8_t *data, size_t len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, .flags = 0 }; gpio_set_level(OLED_DC_PIN, 1); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t)); } static void oled_init(void) { gpio_config_t io_conf = {0}; // DC, CS, RST as outputs io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1ULL<<OLED_DC_PIN) | (1ULL<<OLED_CS_PIN) | (1ULL<<OLED_RST_PIN); gpio_config(&io_conf); // SPI bus init spi_bus_config_t buscfg = { .miso_io_num = OLED_MISO_PIN, .mosi_io_num = OLED_MOSI_PIN, .sclk_io_num = OLED_SCLK_PIN, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = OLED_BUF_SIZE + 8 }; ESP_ERROR_CHECK(spi_bus_initialize(OLED_SPI_HOST, &buscfg, 1)); spi_device_interface_config_t devcfg = { .clock_speed_hz = 8*1000*1000, .mode = 0, .spics_io_num = OLED_CS_PIN, .queue_size = 1, }; ESP_ERROR_CHECK(spi_bus_add_device(OLED_SPI_HOST, &devcfg, &spi_oled)); // Reset sequence gpio_set_level(OLED_RST_PIN, 0); vTaskDelay(pdMS_TO_TICKS(10)); gpio_set_level(OLED_RST_PIN, 1); vTaskDelay(pdMS_TO_TICKS(10)); // Basic init sequence (compatible with many 128x64 controllers) const uint8_t init_cmds[] = { 0xAE, // display off 0xD5, 0x80, // set display clock 0xA8, 0x3F, // multiplex 1/64 0xD3, 0x00, // display offset 0x40, // start line 0x8D, 0x14, // charge pump on 0x20, 0x00, // memory addressing mode horizontal 0xA1, // segment remap 0xC8, // COM scan dec 0xDA, 0x12, // COM pins 0x81, 0xCF, // contrast 0xD9, 0xF1, // precharge 0xDB, 0x40, // vcom detect 0xA4, // resume RAM 0xA6, // normal display 0xAF // display on }; oled_send_cmd(init_cmds, sizeof(init_cmds)); memset(oled_buf, 0, OLED_BUF_SIZE); oled_refresh(); ESP_LOGI(TAG2,"OLED initialized"); } static void oled_refresh(void) { // set column/page addresses const uint8_t setcol[] = {0x21, 0, OLED_WIDTH-1}; const uint8_t setpage[] = {0x22, 0, (OLED_HEIGHT/8)-1}; oled_send_cmd(setcol, sizeof(setcol)); oled_send_cmd(setpage, sizeof(setpage)); oled_send_data(oled_buf, OLED_BUF_SIZE); } static void oled_clear(void) { memset(oled_buf, 0, OLED_BUF_SIZE); oled_refresh(); } // draw rectangle border (byte addressing vertical pages) static void oled_draw_rect(int x,int y,int w,int h) { // naive draw pixel onto buffer for (int yy=y; yy<y+h; yy++) { for (int xx=x; xx<x+w; xx++) { if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } // fill rect static void oled_fill_rect(int x,int y,int w,int h) { oled_draw_rect(x,y,w,h); } static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm) { // simple bitmap: each byte is a column of 8 vertical pixels (LSB top) int bytes_per_col = (h + 7)/8; for (int col=0; col<w; col++) { for (int b=0;b<bytes_per_col;b++) { int src_idx = col*bytes_per_col + b; uint8_t v = bm[src_idx]; for (int bit=0;bit<8;bit++) { int yy = y + b*8 + bit; int xx = x + col; if (yy>=y+h) break; if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy/8; int dst_idx = page*OLED_WIDTH + xx; if (v & (1<<bit)) oled_buf[dst_idx] |= (1<<(yy&7)); } } } } // center simple string using very crude 6x8 font hard-coded for limited characters static const uint8_t font6x8_A[] = { // only include bytes for a few characters used in emoticons and small labels: // We'll support: space, '(', ')', '^', '_', 'o', '-', '<', '3', '>', '!', 'M', 'u', 's', 'i', 'c' // For brevity we won't include a complete font; we'll implement text using small bitmaps manually. }; static void oled_draw_text_centered(const char *txt) { // crude: show at middle as ascii using available characters; we'll just draw as pixel blocks for each char int len = strlen(txt); int char_w = 6; int total_w = len * (char_w+1); int start_x = (OLED_WIDTH - total_w)/2; int y = OLED_HEIGHT/2 - 4; // Simple: draw each char as filled rectangle representing presence (not real glyphs) for demo for (int i=0;i<len;i++) { int x = start_x + i*(char_w+1); if (txt[i] == ' ') continue; // draw small shape to represent char oled_fill_rect(x,y,4,6); } } /*** Mode helpers ***/ static void set_led_rgb(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) { // apply brightness scaling 0-255 uint32_t scale = brightness; uint8_t r2 = (r * scale)/255; uint8_t g2 = (g * scale)/255; uint8_t b2 = (b * scale)/255; led_strip_set_pixel(led_strip, idx, r2, g2, b2); } static void led_update_from_state(void) { xSemaphoreTake(led_mutex, portMAX_DELAY); for (int i=0;i<NUM_LEDS;i++) { uint8_t b = led_brightness[i]; if (b==0) { led_strip_set_pixel(led_strip,i,0,0,0); } else { set_led_rgb(i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2], b); } } led_strip_refresh(led_strip); xSemaphoreGive(led_mutex); } // background LED fade task static void led_fade_task(void *arg) { while (1) { bool changed = false; xSemaphoreTake(led_mutex, portMAX_DELAY); if (g_mode == MODE_LED_KEY_INTERACT) { for (int i=0;i<NUM_LEDS;i++) { if (!led_active[i] && led_brightness[i] > 0) { // fade down slowly uint8_t old = led_brightness[i]; int nb = old - 5; if (nb < 0) nb = 0; led_brightness[i] = nb; changed = true; } } } else if (g_mode == MODE_LED_GRADIENT_CYCLE && gradient_run) { // gradient animation: rotate brightness pattern gradient_pos++; for (int i=0;i<NUM_LEDS;i++) { int pos = (i + gradient_pos) % NUM_LEDS; // wave brightness int b = 80 + (int)(175.0 * (1.0 + sinf((pos*2.0*M_PI)/NUM_LEDS))/2.0); led_brightness[i] = (uint8_t)b; } changed = true; } else { // In normal mode ensure all leds full for (int i=0;i<NUM_LEDS;i++) { if (led_brightness[i] != 255) { led_brightness[i]=255; changed = true; } } } xSemaphoreGive(led_mutex); if (changed) led_update_from_state(); vTaskDelay(pdMS_TO_TICKS(40)); } } /*** Blessing player task (plays in loop while blessing_running) ***/ static void blessing_task(void *arg) { ESP_LOGI(TAG,"Blessing task started"); while (blessing_running) { for (int i=0;i<blessing_len && blessing_running;i++){ int f = blessing[i].freq; int d = blessing[i].dur; if (f>0) { buzzer_play_tone(f); // LED feedback: light one LED corresponding to index (wrap) blessing_led_feedback(i); } else { buzzer_play_tone(0); } int step = d/10; for (int t=0;t<d && blessing_running;t+=10) vTaskDelay(pdMS_TO_TICKS(10)); // small gap buzzer_play_tone(0); vTaskDelay(pdMS_TO_TICKS(30)); } // loop } buzzer_play_tone(0); ESP_LOGI(TAG,"Blessing task ending"); vTaskDelete(NULL); } static void start_blessing_task(void) { if (blessing_running) return; blessing_running = true; xTaskCreatePinnedToCore(blessing_task, "blessing", 4*1024, NULL, 5, &blessing_task_handle, tskNO_AFFINITY); } static void stop_blessing_task(void) { if (!blessing_running) return; blessing_running = false; // task will stop itself blessing_task_handle = NULL; } static void blessing_led_feedback(int step) { xSemaphoreTake(led_mutex, portMAX_DELAY); // simple: light LED at step % NUM_LEDS to full for short time int idx = step % NUM_LEDS; led_brightness[idx] = 255; led_update_from_state(); xSemaphoreGive(led_mutex); } /*** Game logic ***/ static void game_spawn_block(void) { // spawn random region among 0..6 that's empty int tries = 0; while (tries<10) { int r = esp_random() % 7; if (!game_regions[r]) { game_regions[r]=1; break; } tries++; } } static void game_draw(void) { // draw seven regions as columns on OLED oled_clear(); int region_w = OLED_WIDTH / 7; for (int i=0;i<7;i++){ int x = i*region_w + 4; int y = 12; int w = region_w - 8; int h = 40; if (game_regions[i]) { // filled for (int yy=y; yy<y+h; yy++) for (int xx=x;xx<x+w;xx++) { int page = yy/8; int idx = page*OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy&7)); } } else { // outline for (int xx=x;xx<x+w;xx++){ int yy=y; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); yy=y+h-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); } for (int yy=y;yy<y+h;yy++){ int xx=x; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); xx=x+w-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); } } } oled_refresh(); } /*** Main app ***/ void app_main(void) { // init components ESP_LOGI(TAG,"Starting extended piano app"); ws2812b_init(); ttp229_init(); buzzer_init(); oled_init(); // start led fade/animation task xTaskCreate(led_fade_task, "led_fade", 4096, NULL, 5, NULL); ESP_LOGI(TAG,"TTP229 16-key piano extended start!"); // previous key state for edge detection uint16_t prev_keys = 0; while (1) { uint16_t keys = ttp229_read_keys(); // active-low pressed -> bit=1 for pressed // FUNCTION KEYS - these are physical TP4..TP7 (bits 4..7) // detect rising edge (pressed now and not previously pressed) uint16_t newly = (keys & ~prev_keys); // TP4 (bit 4) -> toggle LED-key interaction mode if (newly & (1<<4)) { if (g_mode == MODE_LED_KEY_INTERACT) { g_mode = MODE_NORMAL; ESP_LOGI(TAG,"TP4: back to NORMAL"); oled_clear(); oled_draw_text_centered("^_^ (normal)"); oled_refresh(); } else { g_mode = MODE_LED_KEY_INTERACT; ESP_LOGI(TAG,"TP4: LED-KEY-INTERACT"); oled_clear(); oled_draw_bitmap(56, 28, 16, 8, bm_face1); oled_refresh(); } // small debounce vTaskDelay(pdMS_TO_TICKS(200)); } // TP5 (bit 5) -> toggle gradient cycle if (newly & (1<<5)) { if (g_mode == MODE_LED_GRADIENT_CYCLE) { g_mode = MODE_NORMAL; gradient_run = false; ESP_LOGI(TAG,"TP5: back to NORMAL"); oled_clear(); oled_draw_bitmap(56,28,16,8,bm_face1); oled_refresh(); } else { g_mode = MODE_LED_GRADIENT_CYCLE; gradient_run = true; ESP_LOGI(TAG,"TP5: LED GRADIENT CYCLE"); oled_clear(); oled_draw_bitmap(56,28,16,8,bm_face2); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // TP6 (bit 6) -> toggle blessing play if (newly & (1<<6)) { if (g_mode == MODE_BLESSING_PLAY) { g_mode = MODE_NORMAL; stop_blessing_task(); ESP_LOGI(TAG,"TP6: STOP blessing"); oled_clear(); oled_draw_text_centered("Stopped"); oled_refresh(); } else { g_mode = MODE_BLESSING_PLAY; start_blessing_task(); ESP_LOGI(TAG,"TP6: START blessing"); oled_clear(); oled_draw_bitmap(56,20,12,12,bm_music); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // TP7 (bit 7) -> toggle game if (newly & (1<<7)) { if (g_mode == MODE_GAME) { g_mode = MODE_NORMAL; game_running = false; ESP_LOGI(TAG,"TP7: STOP game"); oled_clear(); oled_draw_text_centered("Bye!"); oled_refresh(); } else { g_mode = MODE_GAME; game_running = true; memset(game_regions,0,sizeof(game_regions)); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800); ESP_LOGI(TAG,"TP7: START game"); oled_clear(); oled_draw_text_centered("Go!"); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // If in game mode, manage spawns & draws if (g_mode == MODE_GAME && game_running) { if (xTaskGetTickCount() > game_next_spawn) { game_spawn_block(); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(1000 + (esp_random()%2000)); game_draw(); } } // handle normal piano key behavior and modes requiring per-key handling if (keys) { // iterate logical keys (i = 0..15) bool played_any = false; for (int i=0;i<16;i++) { uint8_t tp = key_map[i]; if (keys & (1 << tp)) { // pressed // If function key pressed, we already handled on edge; just ignore as piano note if (tp>=4 && tp<=7) continue; int freq = note_freqs[i]; buzzer_play_tone(freq); played_any = true; // Mode specific behavior: if (g_mode == MODE_LED_KEY_INTERACT) { // map key i to LED idx (we'll map first 12 keys to leds) if (i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[i] = 1; // latch on led_brightness[i] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } } else if (g_mode == MODE_NORMAL) { // music visualization on OLED: draw bars based on which keys are pressed oled_draw_music_bars(keys); // show a friendly face small // do not block } else if (g_mode == MODE_BLESSING_PLAY) { // pressing keys can also trigger small sound/LED feedback // light a LED proportional int idx = i % NUM_LEDS; xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } else if (g_mode == MODE_GAME) { // Map certain keys to game regions. // Game expects 7 regions -> map 7 left-most piano keys (i 0..6) if (i < 7) { if (game_regions[i]) { game_regions[i] = 0; // clear block // small success beep buzzer_play_tone(880); vTaskDelay(pdMS_TO_TICKS(80)); buzzer_play_tone(0); game_draw(); } else { // miss sound buzzer_play_tone(220); vTaskDelay(pdMS_TO_TICKS(60)); buzzer_play_tone(0); } } } // end mode cases // For LED gradient cycle, pressing keys can temporarily bump brightness if (g_mode == MODE_LED_GRADIENT_CYCLE) { if (i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[i] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } } // simple small debounce for this key handling // (we don't use goto as before) } else { // key not pressed -> if in LED-key-interact mode, we should mark release for that key_map index // find i -> led index if (g_mode == MODE_LED_KEY_INTERACT) { if (i < NUM_LEDS) { // if it was active and now released, start fade by clearing active if (led_active[i]) { // check previous state had it pressed? simplest: clear active if not pressed // We can't know per-key previous here unless we track prev_keys; we'll use prev_keys below } } } } } // end for keys loop if (!played_any) { /* nothing else */ } // hold tone for small time to avoid chopping vTaskDelay(pdMS_TO_TICKS(40)); // stop tone unless key still pressed // We'll keep continuing while key status remains; main loop will handle } else { // no keys pressed -> silence buzzer buzzer_play_tone(0); } // handle key releases specifically to clear latched led_active when key released uint16_t releases = (~keys) & prev_keys; if (releases) { for (int i=0;i<16;i++) { uint8_t tp = key_map[i]; if (releases & (1<<tp)) { // if it was latched active for LED-key-interact, clear active -> fade begins if (g_mode == MODE_LED_KEY_INTERACT && i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[i] = 0; // fade will reduce brightness in led_fade_task xSemaphoreGive(led_mutex); } } } } prev_keys = keys; vTaskDelay(pdMS_TO_TICKS(20)); } } /*** OLED small helper to show music bars visualization ***/ static void oled_draw_music_bars(uint16_t keys) { memset(oled_buf,0,OLED_BUF_SIZE); // We'll show up to 12 bars across horizontally mapped to 12 leds/keys int bars = NUM_LEDS; int w = OLED_WIDTH / bars; for (int i=0;i<bars;i++) { int x = i*w + 1; int base_y = OLED_HEIGHT - 2; // height depends on whether corresponding key is pressed // find corresponding logical key: reverse map -> find i's logical index if any int bar_h = 6; // simple rule: if any pressed among first 12 keys matching i -> tall bool pressed = false; for (int li=0; li<16; li++) { if (key_map[li] < 16 && li == i) { if (keys & (1 << key_map[li])) pressed = true; } } if (pressed) bar_h = OLED_HEIGHT/2; else bar_h = OLED_HEIGHT/6; // draw rectangle for (int yy = base_y - bar_h; yy <= base_y; yy++) { for (int xx = x; xx < x + w - 2; xx++) { if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy/8; int idx = page*OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy&7)); } } } oled_refresh(); } 这段代码出现了一些问题,出现了无用的参数,删去这些部分,并且tp6键播放blessing出现异常,蜂鸣器持续发生,找出问题所在,修改并重新整合代码
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值