Toggle Navigation
Hatchery
Eggs
BouncyBall
__init__.py
Users
Badges
Login
Register
__init__.py
raw
Content
# By Dan Hagon (@axiomsofchoice) # 05/06/2022 at a rainy EMFCamp # See: https://2022.badge.emfcamp.org/projects/its_raining_at_emfcamp2022 # 24/07/2022 at a fast drying MCH2022 ¯\_(ツ)_/¯ # See: https://mch2022.badge.team/projects/maycontainh2o # MIT License import mch22 import buttons import display #import virtualtimers as vt import audio from machine import Timer from math import sqrt,pow,fabs from random import randint from time import ticks_ms,ticks_diff from .bno055 import init as acc_init from .bno055 import get_accel class Hole(): _x = 0 _y = 0 _r = 0 def set_pos(self,tuple_pos): self._x,self._y = tuple_pos def __init__(self, r=20, x=100,y=100): self.set_pos((x,y)) if (r<4): self._r = 4 else: self._r = r def draw(self, activated=False): if activated: display.drawCircle(self._x, self._y, self._r, 0, 360, True, 0x00FF00) else: display.drawCircle(self._x, self._y, self._r, 0, 360, True, 0x009900) display.drawCircle(self._x, self._y, self._r-4, 0, 360, True, 0x000000) class AnimatedHole(Hole): _ticks_at_creation = 0 _drawable = False _delay = 1000 def __init__(self,r,x,y,delay=500): super().__init__(r,x,y) self._delay = delay self._ticks_at_creation = ticks_ms() def draw(self): if self.drawable(): #display.translate(self._x, self._y) #display.pushMatrix() scalefactor = 1-(ticks_diff(ticks_ms(),self._ticks_at_creation) / (self._delay)) display.drawCircle(self._x, self._y, self._r*scalefactor, 0, 360, False, 0x00FF00) #display.scale(scalefactor,scalefactor) #display.translate(-self._x, -self._y) #display.popMatrix() def reset_time(self): self._ticks_at_creation = ticks_ms() def drawable(self): if ticks_diff(ticks_ms(),self._ticks_at_creation) > self._delay: self._drawable = False else: self._drawable = True return self._drawable class Ball(): class Axis(): vel = 0; pos = 0; pos_min = 0; pos_max = 0; bounce_status = False #used to detect rolling on axis, which is not bouncing was_on_axis = False # ball radius _r = 10 # timekeeping _tick_period=5 bounce_damping = 0.8 def __init__(self,r=5, x_min=0, x_max=100, y_min=0, y_max = 100, tick_period=5): self._r = r self._x = self.Axis() self._y = self.Axis() self._x.pos_min = x_min self._x.pos_max = x_max self._y.pos_min = y_min self._y.pos_max = y_max self._tick_period = tick_period def set_pos(self,tuple_pos): self._x.pos,self._y.pos = tuple_pos def get_pos(self): return(self._x.pos,self._y.pos) def draw(self): display.drawCircle(self._x.pos, self._y.pos, self._r, 0, 360, True, 0xEEEEEE) def update_position(self,acc): self._x = self.update(acc[1], self._x) self._y = self.update(-acc[0], self._y) def update(self, acc, axis): #scaling by amplification by arbitrary factor scaled_acc = float(acc)*10 #print(scaled_acc_x)) axis.vel = axis.vel + scaled_acc / self._tick_period new_axis_pos = axis.pos + axis.vel / self._tick_period #if at window border min if (new_axis_pos < axis.pos_min ): #truncate ball position axis.pos = axis.pos_min if axis.was_on_axis == False: axis.bounce_status = True axis.vel = -axis.vel * self.bounce_damping else: axis.bounce_status = False axis.vel = 0 axis.was_on_axis = True #print(str(display.width()) + " " + str(self.ball_x)) #if at window border max elif (new_axis_pos > axis.pos_max): axis.pos = axis.pos_max if axis.was_on_axis == False: axis.bounce_status = True axis.vel = -axis.vel * self.bounce_damping else: axis.bounce_status = False axis.vel = 0 axis.was_on_axis = True #print(str(display.width()) + " " + str(self.ball_x)) else: axis.pos = new_axis_pos axis.was_on_axis = False axis.bounce_status = False return axis class CircleApp(): def __init__(self, tick_period = 20): ball_radius = 10 self.ball = Ball(ball_radius, ball_radius, display.width()-ball_radius, ball_radius, display.height()-ball_radius, tick_period) self.tick_period = tick_period self.hole=Hole(r=20) self.animated_hole = AnimatedHole(self.hole._r,self.hole._r,self.hole._r) def distance_2D(self,x1,y1,x2,y2): return sqrt(pow(x1-x2,2) + pow(y1-y2,2)) def ball_in_hole(self,tuple_ball,tuple_hole): ball_x,ball_y,ball_r = tuple_ball hole_x,hole_y,hole_r = tuple_hole #Pythagorian distance calculation dist = self.distance_2D(ball_x,ball_y,hole_x, hole_y) # if ball *in* hole if dist < hole_r-ball_r: return True else: return False def ball_on_hole(self,tuple_ball,tuple_hole): ball_x,ball_y,ball_r = tuple_ball hole_x,hole_y,hole_r = tuple_hole #Pythagorian distance calculation dist = self.distance_2D(ball_x,ball_y,hole_x, hole_y) # if ball *on* hole if dist < hole_r+ball_r: return True else: return False def bounced(self): # Return True if bounced and not rolling along one axis. #bounce_vel_threshold = 200 if ( self.ball._x.bounce_status == True) or ( self.ball._y.bounce_status == True): return True return False def animation_tick(self): # MCH2022 black gray display.drawFill(0x333333) raw_acc = get_accel() self.ball.update_position(raw_acc) # Play sound when bouncing with some speed from the border if(self.bounced()): audio.play('/apps/python/bouncyball/sfx_footsteps.mp3', volume=20) #if animation needs to be played (status checked in draw function) self.animated_hole.draw() # Lighten up hole edge if ball is going over the edge of the hole if(self.ball_on_hole( (self.ball._x.pos,self.ball._y.pos,self.ball._r), (self.hole._x,self.hole._y,self.hole._r))): self.hole.draw(activated=True) else: self.hole.draw() self.ball.draw() display.flush() # if ball goes into hole if(self.ball_in_hole( (self.ball._x.pos,self.ball._y.pos,self.ball._r), (self.hole._x,self.hole._y,self.hole._r))): audio.play('/apps/python/bouncyball/sfx_powerup.mp3', volume=20); #start animation self.animated_hole.set_pos((self.hole._x,self.hole._y)) self.animated_hole.reset_time() #do while loop to find new hole location that's some distance away from ball while True: new_hole_x = randint(self.hole._r,display.width()-self.hole._r) new_hole_y = randint(self.hole._r,display.height()-self.hole._r) if(self.distance_2D(new_hole_x,new_hole_y, self.ball._x.pos, self.ball._y.pos) > 20): self.hole.set_pos((new_hole_x,new_hole_y)) break; return self.tick_period def reboot(self, pressed): if pressed: #vt.delete(self.animation_tick) #vt.stop() display.windowRemove(self.window_name) mch22.exit_python() def next_frame(self,tim0): self.call_animation = True def on_activate(self): self.call_animation = False self.window_name = "bouncyball" self.w = display.width() self.h = display.height() self.ball.set_pos((display.width()/2,display.height()/2)) self.hole.set_pos((display.width()/2,display.height()/2)) display.windowCreate(self.window_name,self.w, self.h) acc_init() buttons.attach(buttons.BTN_A, self.reboot) #vt.new(self.tick_period, self.animation_tick) #vt.begin() tim0 = Timer(0) tim0.init(period=self.tick_period,mode=Timer.PERIODIC,callback=self.next_frame) while(True): if self.call_animation: self.animation_tick() app = CircleApp(tick_period=20) app.on_activate()