2. 自动售货机状态机

任务说明

实现一个自动售货机状态机,接受硬币输入(5角、1元),用户选择商品(A:2.5元,B:3元),系统根据累计金额决定是否可出货,并在出货后计算找零。状态机覆盖:等待投币、可选购、出货中、找零完成四个状态。

核心实现

from enum import Enum, auto
from dataclasses import dataclass

class State(Enum):
    IDLE = auto()          # 等待投币
    READY = auto()         # 可选购
    DISPENSING = auto()    # 出货中
    CHANGE = auto()        # 找零中

@dataclass
class Product:
    name: str
    price: float

class VendingMachineFSM:
    def __init__(self):
        self.state = State.IDLE
        self.balance = 0.0
        self.products = {
            'A': Product('可乐', 2.5),
            'B': Product('矿泉水', 3.0)
        }
        self.coin_values = {'5角': 0.5, '1元': 1.0}
        self.change = 0.0
        self.selected_product = None

    def insert_coin(self, coin: str) -> str:
        """投币,返回当前余额"""
        if coin not in self.coin_values:
            return f"无效硬币: {coin}"
        if self.state == State.DISPENSING or self.state == State.CHANGE:
            return "正在处理中,请稍候"
        self.balance += self.coin_values[coin]
        if self.state == State.IDLE:
            self.state = State.READY
        return f"当前余额: {self.balance:.1f}元"

    def select_product(self, code: str) -> str:
        """选择商品,返回结果"""
        if self.state != State.READY:
            return "请先投币"
        if code not in self.products:
            return f"无此商品: {code}"
        product = self.products[code]
        if self.balance < product.price:
            return f"余额不足,还需 {product.price - self.balance:.1f}元"
        # 进入出货状态
        self.state = State.DISPENSING
        self.selected_product = product
        self.balance -= product.price
        self.change = self.balance
        self.balance = 0.0
        return f"出货: {product.name}"

    def dispense(self) -> str:
        """确认出货完成,进入找零状态"""
        if self.state != State.DISPENSING:
            return "当前无出货"
        self.state = State.CHANGE
        return f"出货完成,找零 {self.change:.1f}元"

    def take_change(self) -> str:
        """取走找零,回到初始状态"""
        if self.state != State.CHANGE:
            return "当前无找零"
        change = self.change
        self.change = 0.0
        self.selected_product = None
        self.state = State.IDLE
        return f"找零 {change:.1f}元已取出"

    def cancel(self) -> str:
        """取消购买,退回余额"""
        if self.state == State.DISPENSING or self.state == State.CHANGE:
            return "无法取消,交易进行中"
        refund = self.balance
        self.balance = 0.0
        self.state = State.IDLE
        return f"已取消,退回 {refund:.1f}元"

    def get_state(self) -> str:
        return self.state.name

运行与验证

# 测试脚本
vm = VendingMachineFSM()

# 场景1:投币2.5元购买A商品
print(vm.insert_coin('1元'))    # 余额: 1.0
print(vm.insert_coin('1元'))    # 余额: 2.0
print(vm.insert_coin('5角'))    # 余额: 2.5
print(vm.select_product('A'))   # 出货: 可乐
print(vm.dispense())            # 出货完成,找零 0.0元
print(vm.take_change())         # 找零 0.0元已取出

# 场景2:投币3元购买B商品,找零0.5元
vm2 = VendingMachineFSM()
vm2.insert_coin('1元')
vm2.insert_coin('1元')
vm2.insert_coin('1元')
print(vm2.select_product('B'))  # 出货: 矿泉水
print(vm2.dispense())           # 出货完成,找零 0.5元
print(vm2.take_change())        # 找零 0.5元已取出

# 场景3:余额不足
vm3 = VendingMachineFSM()
vm3.insert_coin('1元')
print(vm3.select_product('A'))  # 余额不足,还需 1.5元

# 场景4:取消购买
vm4 = VendingMachineFSM()
vm4.insert_coin('5角')
print(vm4.cancel())             # 已取消,退回 0.5元

运行上述测试脚本,观察输出是否符合注释中的预期结果。状态机在每次操作后通过get_state()可查询当前状态,验证状态流转是否符合:IDLE → READY → DISPENSING → CHANGE → IDLE。

验证标准

  • 投币后余额正确累加,状态从IDLE转为READY
  • 余额充足时选择商品成功,状态进入DISPENSING,余额扣除商品价格
  • dispense()后状态进入CHANGE,找零金额等于扣款后剩余余额
  • take_change()后状态回到IDLE,所有余额清零
  • 余额不足时拒绝出货,提示缺少金额
  • 取消操作仅在IDLE或READY状态有效,退回全部余额