Skip to content

Instantly share code, notes, and snippets.

@timo

timo/README.md Secret

Last active March 25, 2021 00:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timo/d3a8c871aca93aee4cd8b4fc57b15187 to your computer and use it in GitHub Desktop.
Save timo/d3a8c871aca93aee4cd8b4fc57b15187 to your computer and use it in GitHub Desktop.
Racer Mouse Official Gist

This is a very silly, but fun, input method for mouse movement.

Start it with "start driving", stop it with "stop driving".

Doing the pop noise, or saying "car gas" will make the car start or stop moving. Hissing will turn the car, but hissing for just a short moment will change the turn direction between clockwise and counterclockwise.

say "car reverse" turns the car 180 degrees instantaneously. "car flip" works like the short hiss and changes between clockwise and counterclockwise.

"car nudge" will move the car for a tiny amount of time.

"car boost" will turn the turbo mode on or off.

the car will bounce off the sides of your screen when it hits them.

you can say "car north" with any of the 16 points of the compass (E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, N, NNE, NE, ENE) to set the car in a certain direction, even while driving.

finally, say "car random" to turn a random "demonstration mode" on or off.

The car mode will turn itself off automatically after 45 seconds of no commands / input. Random mode will run longer than that.

BUGS:

  • Before you pop the first time, the car is displayed in the top left corner of the screen, but immediately teleports to the center when started

TODO:

  • Short demonstration video / gif
  • submit noise support to knausj
  • Restrict the car to the focussed window
  • More control over the speed and turn rate of the car
  • "preview" for a given course, perhaps the ability to plan ahead as if editing splines in a vector graphics program
from talon import Module, actions, noise
noise_module = Module()
@noise_module.action_class
class NoiseActions:
def noise_pop():
"""Invoked when the user does the pop noise."""
pass
def noise_hiss_start():
"""Invoked when the user starts hissing (potentially while speaking)"""
pass
def noise_hiss_stop():
"""Invoked when the user finishes hissing (potentially while speaking)"""
pass
def pop_handler(blah):
actions.user.noise_pop()
def hiss_handler(active):
if active:
actions.user.noise_hiss_start()
else:
actions.user.noise_hiss_stop()
noise.register("pop", pop_handler)
noise.register("hiss", hiss_handler)
from talon import Module, Context, canvas, ctrl, cron, ui, actions, app
from talon.types import Point2d
from math import atan2, sin, cos, pi
import time
from random import randrange, normalvariate, choice
racer = Module()
racer_turns_cw = True
racer_speed = 0.0
racer_turning = False
racer_turn_start_time = 0
racer_position = Point2d(0, 0)
racer_angle = 0.0
racer_tick_job = None
racer_random_mode = False
last_input_time = 0
def had_input():
global last_input_time
last_input_time = time.time()
racer.list("point_of_compass", desc="point of compass for race car")
ctx = Context()
direction_name_steps = [
"east", "east south east", "south east", "south south east",
"south", "south south west", "south west", "west south west",
"west", "west north west", "north west", "north north west",
"north", "north north east", "north east", "east north east"]
ctx.lists["self.point_of_compass"] = {
word: str(idx * pi * 2 / len(direction_name_steps)) for (idx, word) in enumerate(direction_name_steps)
}
print(ctx.lists["self.point_of_compass"])
def racer_tick_cb():
global racer_position
global racer_angle
global racer_turning
global racer_turns_cw
if racer_random_mode:
if racer_turning == False and randrange(0, 1000) < 10:
racer_turning = max(0, normalvariate(1, 0.3))
racer_turns_cw = choice([True, False])
if racer_turning != False and randrange(0, 1000) < 15:
racer_turns_cw = normalvariate(5, 2) * choice([-1, 1])
if isinstance(racer_turning, float):
racer_turning -= 1 / 40
if racer_turning <= 0:
racer_turning = False
if isinstance(racer_turns_cw, float):
if racer_turns_cw < 0:
racer_turns_cw += 1 / 40
if racer_turns_cw >= 0:
racer_turns_cw = False
else:
racer_turns_cw -= 1 / 40
if racer_turns_cw >= 0:
racer_turns_cw = True
if racer_speed > 0.0 or racer_turning:
racer_position += Point2d(cos(racer_angle), sin(racer_angle)) * racer_speed * 5
racer_canvas.move(racer_position.x, racer_position.y)
real_pos = racer_position + Point2d(128, 128)
ctrl.mouse_move(real_pos.x, real_pos.y)
if racer_turning and racer_turn_start_time < time.time() - 0.1:
if racer_turns_cw:
racer_angle -= 0.07
else:
racer_angle += 0.07
racer_canvas.show()
if real_pos.x < 0:
racer_angle = vertical_edge_change_angle(racer_angle, True)
racer_position.x = -128 # avoid geting stuck at edge
if real_pos.x >= ui.screens()[0].rect.width:
racer_angle = vertical_edge_change_angle(racer_angle, False)
racer_position.x = ui.screens()[0].rect.width - 1 - 128
if real_pos.y < 0:
racer_angle = horizontal_edge_change_angle(racer_angle, True)
racer_position.y = -128
if real_pos.y >= ui.screens()[0].rect.height:
racer_angle = horizontal_edge_change_angle(racer_angle, False)
racer_position.y = ui.screens()[0].rect.height - 1 - 128
if last_input_time + 45 < time.time():
if racer_random_mode:
if choice([True, False, False, False, False, False, False]):
actions.user.racer_stop()
else:
actions.user.racer_stop()
app.notify("car stopped after 45s of inactivity")
racer_canvas = None
def racer_canvas_draw(canvas):
paint = canvas.paint
paint.color = "00000000"
canvas.translate(128, 128)
canvas.translate(canvas.x, canvas.y)
xpx = cos(-racer_angle + pi/2)
ypx = -sin(-racer_angle + pi/2)
xpy = sin(-racer_angle + pi/2)
ypy = cos(-racer_angle + pi/2)
paint.color = "ff0000ff"
paint.stroke_width = 4
canvas.draw_points(canvas.PointMode.POLYGON,
[Point2d(0, 0),
Point2d(-16 * xpx + -32 * xpy, -16 * ypx + -32 * ypy),
Point2d(-20 * xpy, -20 * ypy),
Point2d(16 * xpx - 32 * xpy, 16 * ypx - 32 * ypy),
Point2d(0, 0)])
direction = 1 if racer_turns_cw else -1
canvas.draw_line(-4 * xpy, -4 * ypy, 16 * xpx * direction - 8 * xpy, 16 * ypx * direction - 8 * ypy)
def vertical_edge_change_angle(angle : float, left : bool) -> float:
"""Change the angle after colliding with vertical edge"""
dx = cos(angle)
dy = sin(angle)
if left and dx < 0 or not left and dx > 0:
dx = - dx
angle = atan2(dy, dx)
return angle
def horizontal_edge_change_angle(angle : float, top : bool) -> float:
"""Change the angle after colliding with horizontal edge"""
dx = cos(angle)
dy = sin(angle)
#print('dx ' + str(dx) + ' dy: ' + str(dy) + ' old angle: ' + str(angle))
if top and dy < 0 or not top and dy > 0:
dy = - dy
angle = atan2(dy, dx)
#print('new angle: ' + str(angle))
return angle
@racer.action_class
class RacerActions:
def racer_start():
"""Starts the "racing" mouse mode"""
global racer_canvas
global racer_position
global racer_tick_job
global racer_speed
had_input()
print("vroom vroom")
if racer_canvas is None:
racer_canvas = canvas.Canvas(0, 0, 256, 256)
racer_position = Point2d(ui.screens()[0].rect.width / 2, ui.screens()[0].rect.height / 2)
# racer_position = Point2d(5, 5)
racer_canvas.register("draw", racer_canvas_draw)
if racer_tick_job:
cron.cancel(racer_tick_job)
racer_tick_job = cron.interval("40ms", racer_tick_cb)
racer_canvas.show()
# racer_canvas.freeze()
def racer_stop():
"""Stops the "racing" mouse mode"""
print("no longer vroom vroom")
cron.cancel(racer_tick_job)
racer_canvas.unregister("draw", racer_canvas_draw)
racer_canvas.hide()
def racer_random(activate: int = -1):
"""Activate or deactivate or toggle the random turn mode"""
global racer_random_mode
had_input()
if activate == -1:
racer_random_mode = not racer_random_mode
else:
racer_random_mode = activate != 0
if racer_random_mode:
actions.user.racer_gas_toggle()
def racer_set_direction(direction: str):
"""Set the direction to a value in radians"""
global racer_angle
had_input()
print("setting direction to ", direction)
racer_angle = float(direction)
def racer_turn_start():
"""Starts turning the "car"."""
global racer_turning
global racer_turn_start_time
racer_turn_start_time = time.time()
had_input()
print("turning...")
racer_turning = True
def racer_turn_stop():
"""Stops turning the "car"."""
global racer_turning
had_input()
if racer_turn_start_time >= time.time() - 0.1:
actions.user.racer_flip_turn_direction()
else:
print("straight ahead")
racer_turning = False
def racer_flip_turn_direction():
"""Changes rotation from CW to CCW and back"""
global racer_turns_cw
had_input()
racer_turns_cw = not racer_turns_cw
print("racer turning ", racer_turns_cw and "clockwise" or "counterclockwise")
def racer_nudge():
"""Drives the car forward a tiny bit"""
global racer_speed
had_input()
racer_speed = 0.1
def reset():
global racer_speed
racer_speed = 0.0
cron.after("500ms", reset)
def racer_gas_toggle():
"""Switch between driving and stopped"""
global racer_speed
had_input()
if racer_speed == 0.0:
racer_speed = 1.0
else:
racer_speed = 0.0
def racer_turbo_toggle():
"""Switch between driving TURBO DRIVING"""
global racer_speed
had_input()
if racer_speed == 1.0:
racer_speed = 5.0
else:
racer_speed = 1.0
def racer_reverse():
"""Turn the racer around 180 degrees"""
global racer_angle
racer_angle += pi
had_input()
-
start driving: user.racer_start()
stop driving: user.racer_stop()
car gas: user.racer_gas_toggle()
car flip: user.racer_flip_turn_direction()
car nudge: user.racer_nudge()
car boost: user.racer_turbo_toggle()
car reverse: user.racer_reverse()
car {user.point_of_compass}: user.racer_set_direction(point_of_compass)
car random: user.racer_random()
action(user.noise_hiss_start): user.racer_turn_start()
action(user.noise_hiss_stop): user.racer_turn_stop()
action(user.noise_pop): user.racer_gas_toggle()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment