2. 自动售货机

任务目标

设计一个自动售货机FSM,接受硬币(1元、5元)和纸币(10元、20元),商品单价为3元。系统根据累计投入金额决定是否出货,并在出货后计算找零。投入金额不足时,等待继续投币或取消退款。

状态定义与转移逻辑

FSM包含4个状态:IDLE(空闲等待)、ACCEPTING(接收投币)、DISPENSE(出货找零)、REFUND(退款)。核心逻辑:

  • IDLE状态下投入任意金额 → 进入ACCEPTING,累计金额
  • ACCEPTING状态下:继续投币则累加金额;累计金额≥3元时触发出货;按下取消键则进入REFUND
  • DISPENSE状态:出货并计算找零(投入金额−3元),完成后回到IDLE
  • REFUND状态:退还全部累计金额,完成后回到IDLE

完整实现(Python)

from enum import Enum

class State(Enum):
    IDLE = 0
    ACCEPTING = 1
    DISPENSE = 2
    REFUND = 3

class VendingMachine:
    def __init__(self, price=3):
        self.price = price
        self.state = State.IDLE
        self.balance = 0

    def insert(self, amount):
        """接受硬币/纸币,返回操作提示"""
        if amount not in (1, 5, 10, 20):
            return f"无效面额: {amount}元"
        if self.state == State.IDLE:
            self.state = State.ACCEPTING
            self.balance = amount
            return self._status()
        elif self.state == State.ACCEPTING:
            self.balance += amount
            if self.balance >= self.price:
                return self._dispense()
            return self._status()
        else:
            return f"当前状态({self.state.name})不可投币"

    def cancel(self):
        """取消交易,退还所有金额"""
        if self.state == State.ACCEPTING:
            refund = self.balance
            self.balance = 0
            self.state = State.IDLE
            return f"已退款 {refund}元"
        return "当前状态不可取消"

    def _dispense(self):
        """出货并找零"""
        change = self.balance - self.price
        self.balance = 0
        self.state = State.IDLE
        msg = f"出货成功,找零 {change}元"
        return msg

    def _status(self):
        return f"已投入 {self.balance}元,还需 {self.price - self.balance}元"

# 最小验证闭环
if __name__ == "__main__":
    vm = VendingMachine()
    print(vm.insert(1))    # 已投入 1元,还需 2元
    print(vm.insert(5))    # 出货成功,找零 3元
    print(vm.insert(10))   # 已投入 10元,还需 0元(状态重置后重新投币)
    print(vm.cancel())     # 已退款 10元

运行与验证

直接运行脚本:python vending_machine.py。预期输出顺序为:

已投入 1元,还需 2元
出货成功,找零 3元
已投入 10元,还需 0元
已退款 10元

验证通过条件:前两行验证投币1元后补投5元,累计6元,出货并找零3元;后两行验证出货后状态重置,新交易投入10元后取消,全额退款。若输出与上述一致,则FSM状态转移正确。

状态转移图

IDLE ACCEPTING DISPENSE REFUND 投币 金额≥3元 出货+找零,回到IDLE 取消 退款完成 继续投币

关键说明

  • 投币面额限定为1、5、10、20元,其他面额直接拒绝;如需扩展面额,修改insert()中的校验元组即可。
  • 出货条件为累计金额≥3元,找零金额自动计算;若投入恰好3元,找零为0元。
  • 取消操作仅在ACCEPTING状态有效,退款后状态回到IDLE,余额清零。