µTHREE – VERSION II
The second version of the µONE is a slight update for three players – each one button and a LED ring as playground. The builtin game can be described as a kind of battle PONG.
trxy_btn.h
// ------------------------------- // TXY'S button helper class // -------------------------------- // serves you wonderful functionality right on the plate :) // is_pressed = momentary state of button // is_holded = momentary state if button is down for at least 1s // on_pressed = triggered once if button is down // on_pressed = triggered once if button is up // on_holded = triggered once if button is down for at least 1s class aBTN { private: int bpin; long ts = 0; bool previous_state = false; bool prev_is_holded = false; bool flipped_phase = false; public: bool is_pressed = false; bool is_holded = false; bool on_pressed = false; bool on_released = false; bool on_holded = false; aBTN(int bpin, bool _flipped_phase) { this->bpin = bpin; this->flipped_phase = _flipped_phase; init(); } void init() { pinMode(bpin, INPUT); } void operateBUTTON() { this->is_pressed = digitalRead(bpin); // flip phase if button phase is flipped if(this->flipped_phase){this->is_pressed = !this->is_pressed;} // reset press states this->on_pressed = false; this->on_released = false; // if there is any change of state :) if (this->previous_state != this->is_pressed) { if (this->is_pressed) { //button is pressed down ------- this->ts = millis(); // set timestamp this->on_pressed = true; } else { //button is released ------- this->on_released = true; this->is_holded = false; this->on_holded = false; this->prev_is_holded = false; } // buffer prev state to avoid repeat this->previous_state = this->is_pressed; } // reset on hold state first this->on_holded = false; // if button is holded for more than 1s and is still pressed! if (this->ts + 1000 < millis() && this->is_pressed ) { this->is_holded = true; // set is holded state each frame here if (this->prev_is_holded != this->is_holded && this->is_pressed ) { this->on_holded = true; this->prev_is_holded = this->is_holded; } } } };
main.cpp
// using the Microduino 328P #include <Arduino.h> #include <Adafruit_NeoPixel.h> #include "trxy_btn.h" #define PIN 10 //LED RING PIN #define buzzpin 3 // buzzer_pin // How many NeoPixels are attached to the Arduino? #define NUMPIXELS 12 int red_score = 10; int blue_score = 10; int green_score = 10; Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ400); #define DELAYVAL 10 // Time (in milliseconds) to pause between pixels aBTN btn1(2,false); // init button 1 on digipin 2 aBTN btn2(11,false); // init button 2 on digipin 4 aBTN btn3(12,false); // hardware layout button to led ring // btn1 >>> LED = 7 // btn2 >>> LED = 3 // btn3 >>> LED = 11 int btn1_pos = 11; int btn2_pos = 3; int btn3_pos = 7; // 0 1r 2g 3b int target_posis_arr[] = {0,11,3,7}; void setup() { Serial.begin(9600); // put your setup code here, to run once: pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) } float cp = 0; float pulse = 0; int target_id = 1; // 1r 2g 3b int next_target_id = 1; float acc = .1; float btn_range_threshold = 1.1; // how forgiving the button reacts / the higher the more forgiving int r = 0; int g = 0; int b = 0; int ccol[] = {11,11,11}; void waiting_for_player(){ for (int i=0;i<12;i++){ ccol[0] = int(random(50)); ccol[1] = int(random(50)); ccol[2] = int(random(50)); // draw the basic color set + mini pulsing pixels.setPixelColor(i, pixels.Color( ccol[0],ccol[1],ccol[2])); } pixels.show(); tone(buzzpin,900+int(random(400)),60); } void idle_anim(){ ccol[0] = (sin(millis()*.001)+1)*20; ccol[1] = (sin(millis()*.0003)+1)*20; ccol[2] = (sin(millis()*.0002)+1)*20; for (int i=0;i<12;i++){ // draw the basic color set + mini pulsing pixels.setPixelColor(i, pixels.Color( ccol[0],ccol[1],ccol[2])); } pixels.show(); } int winnercol[4] ; void calcWinnerStats(int looser_player_id){ // pixels.clear(); winnercol[0]=0; winnercol[1]=0; winnercol[2]=0; int largestValue = max(max(red_score,green_score),blue_score); if(red_score==largestValue){ //red wins! //pixels.setPixelColor(btn1_pos, pixels.Color(255,0,0)); winnercol[0]=155; winnercol[3] = btn1_pos; } if(green_score==largestValue){ //green wins! //pixels.setPixelColor(btn2_pos, pixels.Color(0,255,0)); winnercol[1]=155; winnercol[3] = btn2_pos; } if(blue_score==largestValue){ //blue wins! // pixels.setPixelColor(btn3_pos, pixels.Color(0,0,255)); winnercol[2]=155; winnercol[3] = btn3_pos; } //pixels.show(); //Serial.println("SHOW STATS"); } bool game_running = false; bool intro_running = false; void hitsnd(){ tone(buzzpin,1100+int(random(200)),80); } float general_speed = .04; void hitTheBallBack(){ //float backhit_acc = (random(3) + 7)*.03; // change ball speed // continously get faster!! general_speed += .01; if(acc>0){ acc = -general_speed; }else{ acc = general_speed; } //iterate as long as there is not the same player selected! while( next_target_id == target_id){ next_target_id = int(random(3)+1); } target_id = next_target_id; } int pcp = -1; int intro_count = 0; bool show_stats = false; int stats_show_count = 0; bool does_cursor_pass_target_pos(){ int tp = target_posis_arr[target_id]; // get current target pos if(tp == int(cp) && pcp != int(cp)){ pcp = int(cp); return true;} if(pcp != int(cp)){ pcp = int(cp); } return false; } void restart_game(){ // todo >>> random start pos + dir acc = .1; general_speed = .04; red_score = 10; blue_score = 10; green_score = 10; game_running = true; intro_count = 0; intro_running = false; show_stats = false; stats_show_count = 0; } void showWinnerStats(){ float pulse = abs(sin(millis()*.02)); for (int i=0;i<12;i++){ if(i!= winnercol[3]){ // draw the basic color set + mini pulsing pixels.setPixelColor(i, pixels.Color( winnercol[0]*pulse,winnercol[1]*pulse,winnercol[2]*pulse)); }else{ pixels.setPixelColor(i, pixels.Color( 255,255,255)); } } pixels.show(); stats_show_count++; if(stats_show_count%10){ tone(buzzpin,300+random(800),20); } if( stats_show_count>400){ game_running = false; } } void player_lost( int _playerid){ //tone( buzzpin,1111,200); calcWinnerStats(_playerid); //game_running = false; show_stats = true; //delay(1000); } void loop() { // operate all buttons btn1.operateBUTTON(); btn2.operateBUTTON(); btn3.operateBUTTON(); if(!game_running){ // any button is pressed down for 2s if( btn1.is_holded || btn2.is_holded || btn3.is_holded ){ intro_running = true; } if(intro_running){ intro_count++; } if(intro_count>120){ restart_game(); } // in idle mode! if(intro_count==0){ idle_anim(); }else{ // waiting for players mode! waiting_for_player(); } delay(10); return; }else{ if(show_stats){ showWinnerStats(); delay(10); return; } } // if(!game_running){ showWinnerStats(); return;} // pulse = (sin(millis()*.002)+1)*.5; // wave from 0 to 1 cp += acc; // ------------------------------------ // ---- OBSERVE THE SCORES ----------------- // ---------------------------------- if(red_score<1){ player_lost(1);} if(green_score<1){player_lost(2);} if(blue_score<1){player_lost(3);} if(ccol[0]>red_score/10){ ccol[0] *= .9; }else{ ccol[0] = red_score/10; } if(ccol[1]>green_score/10){ccol[1] *= .9; }else{ ccol[1] = green_score/10; } if(ccol[2]>blue_score/10){ ccol[2] *= .9;}else{ ccol[2] = blue_score/10; } // -------------------------------------------- // ---- OBESERVE THE CURSOR ----------------------- // ----------------------------------------- if( does_cursor_pass_target_pos() ){ // CURSOR PASSES the TARGET AREA! tone(buzzpin,40,16); if( target_id==1 ){ if(red_score>0){red_score--;} } if( target_id==2 ){ if(green_score>0){green_score--; } } if( target_id==3 ){ if(blue_score>0){blue_score--;} } } if(btn1.on_pressed){ if( abs(btn1_pos - cp+1 ) < btn_range_threshold){ if(target_id == 1){ hitsnd(); hitTheBallBack(); ccol[0] = 111; ccol[1] = green_score; ccol[2] = blue_score; red_score+=2; }else{ // pushed button, but not on target! red_score--; } // Serial.println("BUTTON 1"); } } if(btn2.on_pressed){ if( abs(btn2_pos - cp+1 ) < btn_range_threshold){ if(target_id == 2){ hitsnd(); hitTheBallBack(); ccol[0] = red_score; ccol[1] = 111; ccol[2] = blue_score; green_score+=2; }else{ // pushed button, but not on target! green_score--; } } } if(btn3.on_pressed){ if( abs(btn3_pos-cp+1) < btn_range_threshold){ if(target_id == 3){ hitsnd(); hitTheBallBack(); ccol[0] = red_score; ccol[1] = green_score; ccol[2] = 111; blue_score+=2; }else{ // pushed button, but not on target! blue_score--; } // Serial.println("BUTTON 3"); } } // -------------------------------------------- //set the BACKGROUND ----------------------- // ----------------------------------------- for (int i=0;i<12;i++){ // draw the basic color set + mini pulsing pixels.setPixelColor(i, pixels.Color( ccol[0],ccol[1],ccol[2])); } //------------------------------------------ // draw CURSOR IN TARGET COLOR ----------------------- //------------------------------------------ if(target_id == 0){ pixels.setPixelColor(int(cp), pixels.Color(2,2,2)); } if(target_id == 1){ pixels.setPixelColor(int(cp), pixels.Color(122,0,0)); } if(target_id == 2){ pixels.setPixelColor(int(cp), pixels.Color(0,122,0)); } if(target_id == 3){ pixels.setPixelColor(int(cp), pixels.Color(0,0,122)); } // ------------------------------------------------------ // simply light up the aligned LED in the current color when button is down // ------------------------------------------------------ if(btn3.is_pressed){ pixels.setPixelColor(btn3_pos, pixels.Color( 0 ,0,255)); } if(btn2.is_pressed){ pixels.setPixelColor(btn2_pos, pixels.Color( 0 ,255,0)); } if(btn1.is_pressed){ pixels.setPixelColor(btn1_pos, pixels.Color(255,0,0)); } pixels.show(); // Send the updated pixel colors to the hardware. delay(DELAYVAL); // Pause before next pass through loop // make sure, the cursor runs in circles :) if(cp>12){cp=0;} if(cp<0){cp=12;} }
previous prototypes
first prototype attempt
This first prototype kept simplicity in mind – with just one button each player and one shared LED as playground. Although this is quiet handy to work with, the possibilities are a lot of limited – and leads to boring games. So this needed an update 🙂
parts list
in real life
This is still a „UN“proper way to design electronic devices, but anyway this works as ultra rapid prototype in the best manner. With proper usage of freehand soldering, lasercut and a lot of hotglue.
The CODE
As for the setup is very simplistic, we need to distill the most out of what we have from the interface elements. So, here we have some basic helper classes for you to use to get started with your interactions instead of getting stuck with rudimentaries 🙂 – There is three classes, that need to be included to your project – one for the buttons, one for all your led’s one for the incredible sound effects 🙂 – There is no need to change anything in those classes. You can acess everything in your main script. Please read the heads of those scripts to get an idea of their functionality 🙂
trx_btn.h
// ------------------------------- // TXY'S button helper class // -------------------------------- // serves you wonderful functionality right on the plate :) // is_pressed = momentary state of button // is_holded = momentary state if button is down for at least 1s // on_pressed = triggered once if button is down // on_pressed = triggered once if button is up // on_holded = triggered once if button is down for at least 1s class aBTN { private: int bpin; long ts = 0; bool previous_state = false; bool prev_is_holded = false; bool flipped_phase = false; public: bool is_pressed = false; bool is_holded = false; bool on_pressed = false; bool on_released = false; bool on_holded = false; aBTN(int bpin, bool _flipped_phase) { this->bpin = bpin; this->flipped_phase = _flipped_phase; init(); } void init() { pinMode(bpin, INPUT); } void operateBUTTON() { this->is_pressed = digitalRead(bpin); // flip phase if button phase is flipped if(this->flipped_phase){this->is_pressed = !this->is_pressed;} // reset press states this->on_pressed = false; this->on_released = false; // if there is any change of state :) if (this->previous_state != this->is_pressed) { if (this->is_pressed) { //button is pressed down ------- this->ts = millis(); // set timestamp this->on_pressed = true; } else { //button is released ------- this->on_released = true; this->is_holded = false; this->on_holded = false; this->prev_is_holded = false; } // buffer prev state to avoid repeat this->previous_state = this->is_pressed; } // reset on hold state first this->on_holded = false; // if button is holded for more than 1s and is still pressed! if (this->ts + 1000 < millis() && this->is_pressed ) { this->is_holded = true; // set is holded state each frame here if (this->prev_is_holded != this->is_holded && this->is_pressed ) { this->on_holded = true; this->prev_is_holded = this->is_holded; } } } };
trx_led.h
// ------------------------------- // TXY'S LED helper class // -------------------------------- // serves you wonderful functionality right on the plate :) // each LED has its own display mode you can set >>> //0 manual / 1:sinpulse / 2:fading // have a look at the public vars some lines below to see some functionality :) // basic lerp function for fading effects float return_lerp(float _s, int _target, int _time) { _s = _s + (( float(_target) - _s) / float(_time)); return _s; } // -------------- // LED Object // ------------- class aLED { private: int pin; long ts = 0; public: int led_mode = 0; //0 manual / 1:sinpulse / 2:fading int brightness = 0; // current LED brightness ( can be set manually in manual mode) // pulse mode variables mode(1) float pulse_speed = .004; // speed of pulse when in pulsemode the higher, the faster int pulse_amplitude = 125; // how strong is the pulse difference int pulse_offset = 0; // offset the pulse centerpoint // fade mode variables mode(2) float fade_target = 125; // where to fade? 0-254 int fade_speed = 10; // the higher, the slower! aLED(int pin) { this->pin = pin; init(); } void init() { pinMode(pin, OUTPUT); } void operateLED() { if (led_mode == 0) { // if in manual mode // do nothing for now :) } else if (led_mode == 1) { // ----------- PULSE MODE brightness = int((sin(millis() * pulse_speed) + 1) * pulse_amplitude * .5) + pulse_offset; } else if (led_mode == 2) { // ----------- FADING MODE brightness = int(return_lerp(brightness, fade_target, fade_speed)); } // apply to LED analogWrite(pin, brightness); } void setMode(int _mode) { led_mode = _mode; } };
trx_sound.cpp
int buzzer_pin = 9; // setup the buzzer pin float audiotick = 0; int buffer[12] = {0,0,0,0,0,0,0}; // ------------------------------ // --- some fun sounds ------------- // ------------------------------ int blip[] = {1200,600}; int blep[] = {400,1100}; int mel_upupup[] = {800,500,450,400,300}; int mel_downdowndown[] = {300,400,550,600,700}; // this function needs to be executed each step to make the engine work void operate_sfx(){ if(audiotick < 0){ noTone(buzzer_pin); return; }else{ tone(buzzer_pin, buffer[ int(audiotick ) ] ); audiotick -= 0.006; } } // call this function to add tones to the playlist // like: play_sfx(tone_array,number_of_tones_in_array); void play_sfx(int _narr[],int _cnt = 1){ for (int i = 0; i < _cnt ; i++) { buffer[i] = _narr[i]; } audiotick = _cnt; }
Here we have a pretty basic demo script to show off all functions of the pico_one console 🙂
main.cpp
// include libs to make it work! #include <Arduino.h> #include <trx_btn.h> #include <trx_led.h> #include <trx_sound.h> // setup the hardware and helper libs aBTN btn1(8,true); // init button 1 on digipin 2 aBTN btn2(12,true); // init button 2 on digipin 4 aLED led_main(3); // init main LED on PWM pin 6 aLED led_one(5); // init player_one LED on PWM pin 10 aLED led_two(6); // init player_two LED on PWM pin 5 >> do not use pin 11! > conflict with buzzer! void setup() { //Serial.begin(9600); //Serial.println("demo startup"); // first init and set all leds to manual mode with brightness of 50 led_main.setMode(0); led_main.brightness = 50; led_one.setMode(0); led_one.brightness = 50; led_two.setMode(0); led_two.brightness = 50; } void loop() { // each button obj needs to be operated each tick to work btn1.operateBUTTON(); btn2.operateBUTTON(); if(btn1.is_pressed){ led_one.brightness = 200; }else{ led_one.brightness = 0; } if(btn2.is_pressed){ led_two.brightness = 200; }else{ led_two.brightness = 0; } if(btn1.on_pressed){ play_sfx(blep,2); } if(btn2.on_pressed){ play_sfx(blip,2); } if(btn2.on_holded){ // set led to pulse mode at random pulse fequency led_main.setMode(1); led_main.pulse_speed = random(100)*.001+.004; play_sfx(mel_downdowndown,5); } if(btn1.on_holded){ // simply fade the led to a random value and stay there! led_main.setMode(2); led_main.fade_target = int(random(222)+30); led_main.fade_speed = 64; // the higher, the slower! play_sfx(mel_upupup,5); } // each LED obj needs to be operated each tick to work led_main.operateLED(); led_one.operateLED(); led_two.operateLED(); operate_sfx(); // operate the sound effects }