桌面幸运摇“钱”盒 | 摇一摇,解锁今日好运(保姆级教程)

2026-10-191

最近折腾行空板,想着做个有意思的桌面小摆件。正好板载加速度传感器,干脆做个摇“钱”签筒——拿起来晃一晃,就随机跳出一支好运签,还有每日专属祝福、摇签次数统计,放在桌上没事摇两下,仪式感直接拉满。

 

项目只用板载硬件就能跑,不用外接任何元件。下面跟大家拆解下完整实现过程。

 桌面幸运摇“钱”盒 | 摇一摇,解锁今日好运(保姆级教程)_image_1.webp

一、库导入与硬件初始化

 

首先导入依赖库,初始化硬件和全局对象,同时定义几个核心参数:摇签的加速度阈值、防误触冷却时间、最大记录数。

import time
import random
from unihiker import GUI, Audio
from pinpong.board import *
from pinpong.extension.unihiker import *

Board().begin()
gui = GUI()
audio = Audio()

SHAKE_THRESHOLD = 4.0
SHAKE_COOLDOWN = 1.2
MAX_RECORD = 99

这里加速度阈值设为4.0g ,实测正常摇一下很容易达到,又不会轻轻碰一下就误触发;1.2秒冷却时间避免一次摇动被判定成多次。

 

二、内容素材:签文库与每日祝福

 

准备两组数据:签文和每日祝福。

SIGNS :6种运势等级,各配一句签文和专属颜色,从大吉到上签做梯度区分

​DAILY_BLESSINGS :24条祝福语,按日期自动轮换,同一天内固定显示同一条

SIGNS = [
    ("大吉",   "财源广进,好运当头!",  (255, 90, 90)),
    ("上上签", "心想事成,万事如意!",  (255, 150, 40)),
    ("上吉",   "贵人相助,顺风顺水!",  (255, 200, 60)),
    ("中吉",   "小有进步,再接再厉!",  (80, 190, 110)),
    ("小吉",   "稳中向好,喜事可期!",  (60, 170, 220)),
    ("上签",   "桃花盛开,笑容常在!",  (220, 110, 190)),
]

DAILY_BLESSINGS = [
    ("财源滚滚,日进斗金!",     (255, 120, 60)),
    ("身体健康,百病不侵!",     (90, 190, 120)),
    ("学业有成,金榜题名!",     (80, 150, 240)),
    ("事业腾飞,步步高升!",     (120, 110, 220)),
    ("桃花朵朵,甜蜜暴击!",     (230, 100, 170)),
    ("心情美美,天天开心!",     (250, 180, 60)),
    ("心想事成,万事如意!",     (230, 80, 80)),
    ("好运连连,惊喜不断!",     (255, 160, 40)),
    ("平安喜乐,顺遂无忧!",     (90, 170, 190)),
    ("钱包鼓鼓,吃嘛嘛香!",     (240, 140, 70)),
    ("睡个好觉,元气满满!",     (110, 130, 220)),
    ("灵感爆棚,创意无限!",     (200, 120, 220)),
    ("逢考必过,逢赛必胜!",     (80, 180, 140)),
    ("全家安康,福气满堂!",     (210, 90, 90)),
    ("出门遇贵人,办事处处顺!", (255, 170, 90)),
    ("笑口常开,福气自来!",     (240, 150, 50)),
    ("一路绿灯,畅通无阻!",     (70, 170, 110)),
    ("付出有回报,努力有回响!", (100, 150, 230)),
    ("烦恼清零,快乐加倍!",     (250, 130, 160)),
    ("好运常伴,梦想成真!",     (180, 120, 220)),
    ("朋友遍天下,知己长相伴!", (90, 160, 200)),
    ("元气满满,光芒万丈!",     (255, 190, 60)),
    ("所求皆如愿,所行皆坦途!", (140, 180, 90)),
    ("今天也是元气满满的一天!", (250, 140, 80)),
]

def get_daily_blessing():
    t = time.localtime()
    day_index = (t.tm_year * 372 + t.tm_mon * 31 + t.tm_mday) % len(DAILY_BLESSINGS)
    return DAILY_BLESSINGS[day_index]

每日祝福用「年×372 + 月×31 + 日」对总数取模计算索引,不用额外存状态文件,每天自动切换下一条,就算重启板子,当天的祝福也保持不变。

 

三、界面搭建:用基础图形拼出签筒

 

行空板屏幕是 240×320,我把界面从上到下分成标题区、祝福区、签筒区、结果区、状态栏五部分,全部用  unihiker  自带的基础图形和文本函数绘制,不用额外图片资源。

shake_count = 0
last_sign = None
last_level_color = (255, 90, 90)
state = "idle"
result_time = 0

SCREEN_W, SCREEN_H = 240, 320

title = gui.draw_text(x=SCREEN_W//2, y=12, text="幸运摇钱盒",
                      origin="center", font_size=16, color=(120, 60, 160))
subtitle = gui.draw_text(x=SCREEN_W//2, y=34, text="摇一摇 抽好运",
                         origin="center", font_size=10, color=(150, 150, 150))

blessing_txt = gui.draw_text(x=SCREEN_W//2, y=52, text="", origin="center",
                             font_size=10, color=(200, 130, 40))
_blessing_text, _blessing_color = get_daily_blessing()
blessing_txt.config(text="今日祝福: " + _blessing_text, color=_blessing_color)

TUBE_X, TUBE_Y = SCREEN_W//2, 160
TUBE_W, TUBE_H = 84, 96

tube_body  = gui.fill_round_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=TUBE_H,
                                 r=8, color=(200, 50, 50))
tube_rim   = gui.draw_round_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=TUBE_H,
                                 r=8, width=4, color=(255, 200, 60))
tube_mouth = gui.draw_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=12,
                           width=3, color=(255, 220, 110))

sign_lines = [
    gui.draw_line(x0=TUBE_X-28, y0=TUBE_Y-52, x1=TUBE_X-18, y1=TUBE_Y-40,
                  width=5, color=(240, 200, 100)),
    gui.draw_line(x0=TUBE_X-12, y0=TUBE_Y-56, x1=TUBE_X-2,  y1=TUBE_Y-44,
                  width=5, color=(255, 255, 255)),
    gui.draw_line(x0=TUBE_X+4,  y0=TUBE_Y-52, x1=TUBE_X+14, y1=TUBE_Y-40,
                  width=5, color=(240, 200, 100)),
]

deco1 = gui.fill_circle(x=TUBE_X-30, y=TUBE_Y+60, r=6, color=(255, 200, 60))
deco2 = gui.fill_circle(x=TUBE_X,     y=TUBE_Y+60, r=6, color=(255, 200, 60))
deco3 = gui.fill_circle(x=TUBE_X+30,  y=TUBE_Y+60, r=6, color=(255, 200, 60))

result_txt  = gui.draw_text(x=SCREEN_W//2, y=236, text="摇一摇开始",
                            origin="center", font_size=18, color=(90, 90, 90))
result_slip = gui.draw_text(x=SCREEN_W//2, y=264, text="", origin="center",
                            font_size=12, color=(160, 160, 160))

status_title = gui.draw_text(x=12, y=292, text="累计", font_size=11,
                             color=(120, 120, 120))
status_count = gui.draw_digit(x=52, y=288, text="0", font_size=18,
                              color=(0, 150, 120), origin="left")

hint_a = gui.draw_text(x=190, y=292, text="A:统计", font_size=11,
                       color=(160, 160, 160))
hint_b = gui.draw_text(x=190, y=306, text="B:换签", font_size=11,
                       color=(160, 160, 160))

签筒用圆角矩形做红色筒身,加金色边框和筒口,里面画三条斜线当露出的签头,视觉上很有那味儿。所有控件都提前创建好,后面用  config()  更新属性,避免反复新建对象导致界面卡顿。

 

四、核心逻辑:摇动检测与抽签动画

 

这部分是项目的灵魂,分成三块:摇动检测、摇签动画、界面自动复位。

 

1. 计数更新与摇动动画

 

先写两个工具函数:更新计数显示、播放签筒左右晃动的动画。

def update_count_display():
    status_count.config(text=str(shake_count))

def play_shake_animation(iterations=6):
    for i in range(iterations):
        offset = 10 if i % 2 == 0 else -10
        tube_body.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        tube_rim.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        tube_mouth.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        sign_lines[0].config(x0=TUBE_X-28+offset)
        sign_lines[0].config(x1=TUBE_X-18+offset)
        sign_lines[1].config(x0=TUBE_X-12+offset)
        sign_lines[1].config(x1=TUBE_X-2+offset)
        sign_lines[2].config(x0=TUBE_X+4+offset)
        sign_lines[2].config(x1=TUBE_X+14+offset)
        time.sleep(0.06)

    tube_body.config(x=TUBE_X-42, y=TUBE_Y-46)
    tube_rim.config(x=TUBE_X-42, y=TUBE_Y-46)
    tube_mouth.config(x=TUBE_X-42, y=TUBE_Y-46)
    sign_lines[0].config(x0=TUBE_X-28, x1=TUBE_X-18)
    sign_lines[1].config(x0=TUBE_X-12, x1=TUBE_X-2)
    sign_lines[2].config(x0=TUBE_X+4,  x1=TUBE_X+14)

 

2. 飞签动画与抽签执行

 

摇完之后播放一支签从筒口飞出的动画,然后随机抽取签文、更新结果和计数。

def draw_flying_slip(level, color):
    slip = gui.draw_line(x0=TUBE_X-3, y0=TUBE_Y-58, x1=TUBE_X+3, y1=TUBE_Y-82,
                         width=5, color=color)
    y_start = TUBE_Y - 58
    y_end = 205
    for step in range(5 + 1):
        yy = y_start + (y_end - y_start) * step // 5
        slip.config(y0=yy, y1=yy-24)
        time.sleep(0.05)
    result_txt.config(text=level, color=color)
    result_slip.config(text=last_sign)

def do_shake_draw():
    global shake_count, last_sign, state, result_time, last_level_color

    state = "shaking"
    play_shake_animation()

    level, text, color = random.choice(SIGNS)
    last_sign = text
    last_level_color = color
    shake_count += 1

    state = "result"
    result_time = time.time()

    draw_flying_slip(level, color)
    update_count_display()

    print("抽到[{}]: {}".format(level, text))

 

3. 摇动检测线程

 

开一个独立线程持续读取加速度传感器,满足条件就触发摇签,不阻塞主界面

def shake_detect_loop():
    global state, result_time
    last_trigger = 0.0
    while True:
        strength = accelerometer.get_strength()
        now = time.time()

        if (strength > SHAKE_THRESHOLD
                and now - last_trigger > SHAKE_COOLDOWN
                and state != "shaking"):
            last_trigger = now
            do_shake_draw()

        time.sleep(0.02)

 

4. 界面自动复位

 

再开一个UI线程,结果展示3秒后自动回到待机界面,恢复显示每日祝福。

def sign_stats_loop():
    global state
    while True:
        if state == "result" and time.time() - result_time > 3:
            result_txt.config(text="摇一摇开始", color=(90, 90, 90))
            result_slip.config(text="")
            blessing_txt.config(text="今日祝福: " + _blessing_text, color=_blessing_color)
            state = "idle"
        time.sleep(0.1)

 

五、按键功能:统计与洗牌

 

把行空板的A、B两个按键利用起来:

A键:查看累计摇签次数 + 当前环境音量

​B键:打乱签文库顺序,相当于「洗牌换手气」

def show_stats():
    vol = audio.sound_level()
    result_txt.config(text="已摇 {} 次".format(shake_count), color=(0, 150, 120))
    result_slip.config(text="环境音量 {}%".format(vol), color=(160, 160, 160))

def reshuffle_pool():
    random.shuffle(SIGNS)
    result_txt.config(text="签库已洗牌!", color=(120, 60, 160))
    result_slip.config(text="再摇一摇试试手气", color=(160, 160, 160))

gui.on_a_click(show_stats)
gui.on_b_click(reshuffle_pool)

 

六、启动程序

 

最后启动两个子线程,主循环保持程序运行即可,行空板的GUI不需要手动  update()。

shake_thread = gui.start_thread(shake_detect_loop)
ui_thread = gui.start_thread(sign_stats_loop)

print("幸运摇钱盒已启动:摇动行空板(加速度强度>4)抽好运签!")
print("今日祝福[{}]: {}".format(time.strftime("%Y-%m-%d"), _blessing_text))

while True:
    time.sleep(0.1)

 

七、完整代码

 

import time
import random
from unihiker import GUI, Audio
from pinpong.board import *
from pinpong.extension.unihiker import *

Board().begin()
gui = GUI()
audio = Audio()

SHAKE_THRESHOLD = 4.0
SHAKE_COOLDOWN = 1.2
MAX_RECORD = 99

SIGNS = [
    ("大吉",   "财源广进,好运当头!",  (255, 90, 90)),
    ("上上签", "心想事成,万事如意!",  (255, 150, 40)),
    ("上吉",   "贵人相助,顺风顺水!",  (255, 200, 60)),
    ("中吉",   "小有进步,再接再厉!",  (80, 190, 110)),
    ("小吉",   "稳中向好,喜事可期!",  (60, 170, 220)),
    ("上签",   "桃花盛开,笑容常在!",  (220, 110, 190)),
]

DAILY_BLESSINGS = [
    ("财源滚滚,日进斗金!",     (255, 120, 60)),
    ("身体健康,百病不侵!",     (90, 190, 120)),
    ("学业有成,金榜题名!",     (80, 150, 240)),
    ("事业腾飞,步步高升!",     (120, 110, 220)),
    ("桃花朵朵,甜蜜暴击!",     (230, 100, 170)),
    ("心情美美,天天开心!",     (250, 180, 60)),
    ("心想事成,万事如意!",     (230, 80, 80)),
    ("好运连连,惊喜不断!",     (255, 160, 40)),
    ("平安喜乐,顺遂无忧!",     (90, 170, 190)),
    ("钱包鼓鼓,吃嘛嘛香!",     (240, 140, 70)),
    ("睡个好觉,元气满满!",     (110, 130, 220)),
    ("灵感爆棚,创意无限!",     (200, 120, 220)),
    ("逢考必过,逢赛必胜!",     (80, 180, 140)),
    ("全家安康,福气满堂!",     (210, 90, 90)),
    ("出门遇贵人,办事处处顺!", (255, 170, 90)),
    ("笑口常开,福气自来!",     (240, 150, 50)),
    ("一路绿灯,畅通无阻!",     (70, 170, 110)),
    ("付出有回报,努力有回响!", (100, 150, 230)),
    ("烦恼清零,快乐加倍!",     (250, 130, 160)),
    ("好运常伴,梦想成真!",     (180, 120, 220)),
    ("朋友遍天下,知己长相伴!", (90, 160, 200)),
    ("元气满满,光芒万丈!",     (255, 190, 60)),
    ("所求皆如愿,所行皆坦途!", (140, 180, 90)),
    ("今天也是元气满满的一天!", (250, 140, 80)),
]

def get_daily_blessing():
    t = time.localtime()
    day_index = (t.tm_year * 372 + t.tm_mon * 31 + t.tm_mday) % len(DAILY_BLESSINGS)
    return DAILY_BLESSINGS[day_index]

shake_count = 0
last_sign = None
last_level_color = (255, 90, 90)
state = "idle"
result_time = 0

SCREEN_W, SCREEN_H = 240, 320

title = gui.draw_text(x=SCREEN_W//2, y=12, text="幸运摇钱盒",
                      origin="center", font_size=16, color=(120, 60, 160))
subtitle = gui.draw_text(x=SCREEN_W//2, y=34, text="摇一摇 抽好运",
                         origin="center", font_size=10, color=(150, 150, 150))

blessing_txt = gui.draw_text(x=SCREEN_W//2, y=52, text="", origin="center",
                             font_size=10, color=(200, 130, 40))
_blessing_text, _blessing_color = get_daily_blessing()
blessing_txt.config(text="今日祝福: " + _blessing_text, color=_blessing_color)

TUBE_X, TUBE_Y = SCREEN_W//2, 160
TUBE_W, TUBE_H = 84, 96

tube_body  = gui.fill_round_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=TUBE_H,
                                 r=8, color=(200, 50, 50))
tube_rim   = gui.draw_round_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=TUBE_H,
                                 r=8, width=4, color=(255, 200, 60))
tube_mouth = gui.draw_rect(x=TUBE_X-42, y=TUBE_Y-46, w=TUBE_W, h=12,
                           width=3, color=(255, 220, 110))

sign_lines = [
    gui.draw_line(x0=TUBE_X-28, y0=TUBE_Y-52, x1=TUBE_X-18, y1=TUBE_Y-40,
                  width=5, color=(240, 200, 100)),
    gui.draw_line(x0=TUBE_X-12, y0=TUBE_Y-56, x1=TUBE_X-2,  y1=TUBE_Y-44,
                  width=5, color=(255, 255, 255)),
    gui.draw_line(x0=TUBE_X+4,  y0=TUBE_Y-52, x1=TUBE_X+14, y1=TUBE_Y-40,
                  width=5, color=(240, 200, 100)),
]

deco1 = gui.fill_circle(x=TUBE_X-30, y=TUBE_Y+60, r=6, color=(255, 200, 60))
deco2 = gui.fill_circle(x=TUBE_X,     y=TUBE_Y+60, r=6, color=(255, 200, 60))
deco3 = gui.fill_circle(x=TUBE_X+30,  y=TUBE_Y+60, r=6, color=(255, 200, 60))

result_txt  = gui.draw_text(x=SCREEN_W//2, y=236, text="摇一摇开始",
                            origin="center", font_size=18, color=(90, 90, 90))
result_slip = gui.draw_text(x=SCREEN_W//2, y=264, text="", origin="center",
                            font_size=12, color=(160, 160, 160))

status_title = gui.draw_text(x=12, y=292, text="累计", font_size=11,
                             color=(120, 120, 120))
status_count = gui.draw_digit(x=52, y=288, text="0", font_size=18,
                              color=(0, 150, 120), origin="left")

hint_a = gui.draw_text(x=190, y=292, text="A:统计", font_size=11,
                       color=(160, 160, 160))
hint_b = gui.draw_text(x=190, y=306, text="B:换签", font_size=11,
                       color=(160, 160, 160))

def update_count_display():
    status_count.config(text=str(shake_count))

def play_shake_animation(iterations=6):
    for i in range(iterations):
        offset = 10 if i % 2 == 0 else -10
        tube_body.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        tube_rim.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        tube_mouth.config(x=TUBE_X-42+offset, y=TUBE_Y-46)
        sign_lines[0].config(x0=TUBE_X-28+offset)
        sign_lines[0].config(x1=TUBE_X-18+offset)
        sign_lines[1].config(x0=TUBE_X-12+offset)
        sign_lines[1].config(x1=TUBE_X-2+offset)
        sign_lines[2].config(x0=TUBE_X+4+offset)
        sign_lines[2].config(x1=TUBE_X+14+offset)
        time.sleep(0.06)

    tube_body.config(x=TUBE_X-42, y=TUBE_Y-46)
    tube_rim.config(x=TUBE_X-42, y=TUBE_Y-46)
    tube_mouth.config(x=TUBE_X-42, y=TUBE_Y-46)
    sign_lines[0].config(x0=TUBE_X-28, x1=TUBE_X-18)
    sign_lines[1].config(x0=TUBE_X-12, x1=TUBE_X-2)
    sign_lines[2].config(x0=TUBE_X+4,  x1=TUBE_X+14)

def draw_flying_slip(level, color):
    slip = gui.draw_line(x0=TUBE_X-3, y0=TUBE_Y-58, x1=TUBE_X+3, y1=TUBE_Y-82,
                         width=5, color=color)
    y_start = TUBE_Y - 58
    y_end = 205
    for step in range(5 + 1):
        yy = y_start + (y_end - y_start) * step // 5
        slip.config(y0=yy, y1=yy-24)
        time.sleep(0.05)
    result_txt.config(text=level, color=color)
    result_slip.config(text=last_sign)

def do_shake_draw():
    global shake_count, last_sign, state, result_time, last_level_color

    state = "shaking"
    play_shake_animation()

    level, text, color = random.choice(SIGNS)
    last_sign = text
    last_level_color = color
    shake_count += 1

    state = "result"
    result_time = time.time()

    draw_flying_slip(level, color)
    update_count_display()

    print("抽到[{}]: {}".format(level, text))

def shake_detect_loop():
    global state, result_time
    last_trigger = 0.0
    while True:
        strength = accelerometer.get_strength()
        now = time.time()

        if (strength > SHAKE_THRESHOLD
                and now - last_trigger > SHAKE_COOLDOWN
                and state != "shaking"):
            last_trigger = now
            do_shake_draw()

        time.sleep(0.02)

def sign_stats_loop():
    global state
    while True:
        if state == "result" and time.time() - result_time > 3:
            result_txt.config(text="摇一摇开始", color=(90, 90, 90))
            result_slip.config(text="")
            blessing_txt.config(text="今日祝福: " + _blessing_text, color=_blessing_color)
            state = "idle"
        time.sleep(0.1)

def show_stats():
    vol = audio.sound_level()
    result_txt.config(text="已摇 {} 次".format(shake_count), color=(0, 150, 120))
    result_slip.config(text="环境音量 {}%".format(vol), color=(160, 160, 160))

def reshuffle_pool():
    random.shuffle(SIGNS)
    result_txt.config(text="签库已洗牌!", color=(120, 60, 160))
    result_slip.config(text="再摇一摇试试手气", color=(160, 160, 160))

gui.on_a_click(show_stats)
gui.on_b_click(reshuffle_pool)

shake_thread = gui.start_thread(shake_detect_loop)
ui_thread = gui.start_thread(sign_stats_loop)

print("幸运摇钱盒已启动:摇动行空板(加速度强度>4)抽好运签!")
print("今日祝福[{}]: {}".format(time.strftime("%Y-%m-%d"), _blessing_text))

while True:
    time.sleep(0.1)

 

效果展示

桌面幸运摇“钱”盒 | 摇一摇,解锁今日好运(保姆级教程)_image_2.webp
桌面幸运摇“钱”盒 | 摇一摇,解锁今日好运(保姆级教程)_image_3.webp

 

项目总结

 

这个小项目虽然代码不多,但互动感很强,摇一摇的操作很自然,放在桌面当个解压小玩具特别合适。

 

核心用到的行空板特性:

  • 板载加速度传感器作为交互输入
  • ​原生GUI函数纯代码绘制界面,零资源依赖
  • ​多线程分离硬件检测和UI逻辑,运行流畅
  • ​板载按键做扩展功能,玩法更丰富

 

大家有兴趣的话还可以继续拓展:加上摇签音效、把摇签记录存在本地、增加更多签文和运势等级,甚至加上联网功能同步每日黄历~

 

最后如果你不熟悉代码可以仿照我的思路使用我的“万年历”扩展库,在mind+V2 Python积木模式扩展搜索万年历,找到作者lumens的那个,里面有较为全面的万年历功能

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -