OO Unit2 总结

张振羽-24373282 2026-04-29 22:41:57

OO Unit2

一、同步块的设置和锁的选择

在三次作业中,我对线程间的通信采用了生产者-消费者模型,生产者-消费者模型中同步块和锁通常设置在对队列的临界操作中,我只需要保证队列同时只有一个线程访问,即可保证线程不会出现交叉运行的错误。在生产者-消费者模型中,通常生产者-消费者内部的代码运行时间较长,因此对队列设置严格的锁对多线程程序的效率影响不大。

同时,我在双轿厢的设计中,使用了类似读写锁的分权限/优先级的锁结构,来维护双轿厢不会同时处于 F2 中。为什么采用这种独特的锁呢?因为我设计的是当某一轿厢想要获取 F2 时,他将检查状态,若 F2 处于强锁状态,那么就等待,若处于弱锁状态,那么就驱逐,如果无锁,那么就获锁。这种结构允许我能够临时驱逐 F2。

我对锁和同步块的理解就是,一个资源如果与时序强相关或者说我需要原子化的操作,我就需要获锁来保证当前操作对该资源是原子的。

二、三次作业中调度器的设计

这个调度器设计真是这次作业的核心科技了(

在第一次作业中,我就初步判断,对于底层的单个电梯,他很难感知到全局状态而作出很多优化,如果我需要让他感知全局状态,我就需要改为由一个统一管理 6 个电梯的管理器向电梯投放单步操作,如:开门、关门、上升、下降。这种结构是相当不优雅且难扩展难维护的。因此我放弃了这部分优化(踢人优化)仅仅设置单个电梯执行 look 算法,然后 dispatcher 撑起优化的大梁。这种结构的好处就是会将决策简化为将乘客分配给哪个电梯。于是,白箱的极限,影子电梯应运而生。

第一次作业不涉及 dispatcher

第二次作业中,我尝试了影子电梯,并取得较好的成果。影子电梯就是为每个电梯提供结构使得可以计算当前分配的情况下,一直到停止运行耗费的代价。那么我们 dispatch 就变成了 “模拟然后比大小” 如此简单的过程。

第三次作业中,我尝试了调参,然后付出了惨痛的代价(因为测出来代码退化之后来不及提交原来的代码了)

正如上面所说,影子电梯就是贪心的极限,他直接以性能值为优化目标,所以可以很好适应时间、电量等多种性能指标。

三、出现过的bug和面对多线程bug的debug方法

1、 边界细节问题:

  • 电梯唤醒的条件漏写导致唤醒异常

对于这类问题我们可以使用 AI 辅助静态检查或者生成随机数据测试。


2、 线程协同 和 锁:

  • 程序结束终止条件判断错误,很容易写出线程不会自动结束的bug,在分派过程中丢失信号导致无法正常结束。解决方法是统计请求数和完成的请求数来决定是否结束
  • CPU超时 通常来讲是写出轮询,如:dispatcher 不停检查结束条件。解决方法是使用 wait notify 而非 while () { check() }
  • 锁的范围不够明确,容易写出竞争性访问或死锁
  • 原子性问题 对 F2 加锁,读和写之间有短暂的释放,导致不同步

3、 dispatcher

  • 容易将请求积压到一个电梯。解决方法是完善simulator,完整的计算整个性能值

4、 多线程的调试

I 压测

很多多线程相关的 bug 并不是稳定触发的:如原子性问题;这些 bug 既不好动态调试定位,也不好静态调试观察。

我们通过压测来定位 bug,并保留出现 bug 时的上下文状态。

II 静态查错

在通过压测找到 bug 位置或出现原因等相关信息后,调试将会简单很多。

我们可以通过读取堆栈、变量状态,bug 附近代码,来进行静态调试,此时也可以很方便的使用 ai 辅助调试。

总结

本次会议讨论了常见的 bug 原因和解决方案,以及多线程调试等问题。

在多线程程序中,通常寻找 bug 和调试的工作会较为麻烦,bug的种类繁多。

本次作业中常出现的bug围绕:边界细节,线程协同,锁,以及 dispacher。

多线程调试的技巧是压测和获取出现bug时的状态。

四、大模型的使用心得

大模型太好用了你们知道吗(

本次主要使用了 codex-gpt-5.4

其实主要是让 AI 帮忙完善了一下影子电梯的 Simulator,其他的代码方面实在是不敢让 AI 帮忙,一个是会写出很奇怪的调度策略,另一个是 AI 在多线程问题上,如果你让他放飞自我的话,他会直接写出死锁,还有一点就是,AI 真的超级喜欢写轮询,这太坏了!

另外在互测的时候 AI 真的是帮了大忙,首先让 AI 光速写了个 spj 程序,然后又让 AI 写了包括数据生成器在内的全套评测机。帮我在 HW3 大杀四方。而且主要是省时间,感觉现在六系的课业负担实在太重,把需求扔给 AI 然后让他自己跑还是相当省心。

总的来说,AI 在这次作业中体现到的就是,他有很多莫名其妙的小巧思,然后就给自己写出问题了,而且很难精确的描述需求。

五、真实感受

面向对象作业负担太重了太重了太重了太重了

首先性能分的设计让内卷永无止境,我认为应当适当放宽满分的设置,尤其是在这种电梯调度类似最优化的题目上,总有神人的灵机一动虽然过不了很多数据,但是能对某个测试点特攻。然后评测 CD 实在是小巧思拉满,现在评测机跑的也不慢,我说要不放宽评测冷却或者干脆直接去掉,这让我安排作业的时间会相当受限制。毕竟都已经有了提交次数限制。

最最最最神中神的一点是!!!!互测 CD 整整 30 min,在仅仅考虑拿 base 分的情况下,A 房同学需要花费宝贵的 2.5h 守在电脑前面然后连着提交,而 C 房同学来到了惊人的 5h,大家是不用上课天天守着电脑光想着卡点提交吗。如果我想要好好叉人那更是灾难,本人亲身体验 Unit1 HW3 想叉人,光启动测试就花了够长的时间了,测出来的5,6个 bug 一直给我叉到凌晨,到了 Unit2 更是变态,多线程 bug 不稳定触发 + 超长冷却,你就叉吧,我 Unit2 HW2 HW3 连续两次每次都是整整占我一整天,冷却中间的 30min 请问我拿来干什么呢,写别的作业时间又太短,很容易卡不上点提交bug,然后这 30min 还特别容易忘掉叉过哪些bug。现实情况就是,我在本地测到了3,4个多线程的bug,硬等 等两三个小时,然后交上去发现一个也叉不到,这集神了。

中测强度太低了!!!

再回到这次作业本身,电梯调度,但是大家卷出了踢人、量子电梯、一直开门等人等离谱的不贴合实际的优化。我认为还需要增加更多的限制来使得题目能够更加贴近现实,如:若本层在 0.4s 内没有继续上人就必须关门,电梯想要移动之前必须提前输出启动信息等等。同时,这种更加强的限制更能使得内卷程度下降。

总的来说,这个调度/优化这个课题虽然不在我的优势上,但是能够写一个这样的作业得到锻炼还是很不错的。

附件

附上我的spj吧,其实数据投喂器我逆了一个并且完善了一下(但是我还没来得及看时间倒流这个神秘bug)(其实python打包的程序随便逆,ctf 里已经做过很多了),但是我有点懒得贴上来数据投喂器,我自己写的有点丑了

golang 是这个世界上最好的语言!

package main

import (
    "bufio"
    "errors"
    "flag"
    "fmt"
    "os"
    "sort"
    "strconv"
    "strings"
)

const (
    tick          = 10000
    moveNormal    = 4000
    moveTest      = 2000
    doorGap       = 4000
    stopGap       = 10000
    maintLimit    = 70000
    updateLimit   = 60000
    recycleLimit  = 60000
    maxLoad       = 400
)

type mode int

const (
    modeNormal mode = iota
    modeRepAccept
    modeRepair
    modeTest
    modeUpAccept
    modeUpdate
    modeDouble
    modeRecAccept
    modeRecycle
)

type floor int

const (
    b4 floor = iota
    b3
    b2
    b1
    f1
    f2
    f3
    f4
    f5
    f6
    f7
)

var floorNames = []string{
    "B4", "B3", "B2", "B1", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
}

var floorIndex = map[string]floor{
    "B4": b4, "B3": b3, "B2": b2, "B1": b1,
    "F1": f1, "F2": f2, "F3": f3, "F4": f4, "F5": f5, "F6": f6, "F7": f7,
}

type passengerReq struct {
    ID     int
    Weight int
    From   floor
    To     floor
    Time   int
}

type maintReq struct {
    ElevatorID int
    WorkerID   int
    Target     floor
    Time       int
}

type updateReq struct {
    ElevatorID int
    Time       int
}

type recycleReq struct {
    BackupID int
    Time     int
}

type caseData struct {
    Passengers         map[int]*passengerReq
    PassengerIDs       []int
    MaintsByElevator   map[int][]maintReq
    UpdatesByElevator  map[int][]updateReq
    RecyclesByBackup   map[int][]recycleReq
}

type passengerState struct {
    AssignedCar int
    InsideCar   int
    OutsideAt   floor
    Finished    bool
    FinalTime   int
}

type workerState struct {
    InCar     int
    OutsideAt floor
    Finished  bool
}

type maintRun struct {
    AcceptAt int
    BeginAt  int
    WorkerID int
    Target   floor
    Phase    string
}

type updateRun struct {
    AcceptAt int
    BeginAt  int
}

type recycleRun struct {
    AcceptAt int
    BeginAt  int
}

type timeValue struct {
    Time  int
    Value int
}

type modeValue struct {
    Time  int
    Value mode
}

type carState struct {
    ID               int
    Shaft            int
    Main             bool
    Active           bool
    Floor            floor
    DoorOpen         bool
    OrdinaryLoad     int
    OrdinaryIDs      map[int]struct{}
    WorkerIDs        map[int]struct{}
    LastPhysicalTime int
    LastOpenTime     int
    ReceiveCount     int
    ReceiveHistory   []timeValue
}

type shaftState struct {
    ID          int
    Mode        mode
    ModeHistory []modeValue
    Maint       *maintRun
    Update      *updateRun
    Recycle     *recycleRun
}

type metrics struct {
    Trun float64
    Tavg float64
    W    float64
}

type event struct {
    Time     int
    Raw      string
    Kind     string
    SubKind  string
    PersonID int
    Elevator int
    WorkerID int
    Floor    floor
    Target   floor
}

type judge struct {
    data           *caseData
    passengers     map[int]*passengerState
    workers        map[int]*workerState
    cars           map[int]*carState
    shafts         map[int]*shaftState
    maintIndex     map[int]int
    updateIndex    map[int]int
    recycleIndex   map[int]int
    lineNo         int
    lastOutputTime int
    arriveCount    int
    openCount      int
    closeCount     int
}

func main() {
    inputPath := flag.String("input", "", "input file")
    outputPath := flag.String("output", "", "output file")
    realTime := flag.Float64("real", -1, "real time in seconds")
    cpuTime := flag.Float64("cpu", -1, "cpu time in seconds")
    tmax := flag.Float64("tmax", 180, "time limit in seconds")
    flag.Parse()

    if *inputPath == "" || *outputPath == "" {
        failf("usage: go run spj.go -input case.txt -output out.txt [-real 12.3] [-cpu 0.8] [-tmax 180]")
    }

    data, err := parseInputFile(*inputPath)
    if err != nil {
        failf("input parse error: %v", err)
    }
    j := newJudge(data)
    if err := j.judgeOutput(*outputPath); err != nil {
        failf("WA: %v", err)
    }
    res, err := j.finish(*realTime, *cpuTime, *tmax)
    if err != nil {
        failf("WA: %v", err)
    }

    fmt.Println("OK")
    fmt.Printf("Tfinal=%.4f\n", seconds(j.lastOutputTime))
    if *realTime >= 0 {
        fmt.Printf("Treal=%.4f\n", *realTime)
        fmt.Printf("Trun=%.4f\n", res.Trun)
    }
    fmt.Printf("Tavg=%.4f\n", res.Tavg)
    fmt.Printf("W=%.1f\n", res.W)
    fmt.Printf("NARRIVE=%d\n", j.arriveCount)
    fmt.Printf("NOPEN=%d\n", j.openCount)
    fmt.Printf("NCLOSE=%d\n", j.closeCount)
    if *cpuTime >= 0 {
        fmt.Printf("CPU=%.4f\n", *cpuTime)
    }
}

func parseInputFile(path string) (*caseData, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    data := &caseData{
        Passengers:        make(map[int]*passengerReq),
        MaintsByElevator:  make(map[int][]maintReq),
        UpdatesByElevator: make(map[int][]updateReq),
        RecyclesByBackup:  make(map[int][]recycleReq),
    }

    scanner := bufio.NewScanner(file)
    lineNo := 0
    for scanner.Scan() {
        lineNo++
        line := strings.TrimSpace(scanner.Text())
        if line == "" {
            continue
        }
        if err := parseInputLine(data, line); err != nil {
            return nil, fmt.Errorf("line %d: %w", lineNo, err)
        }
    }
    if err := scanner.Err(); err != nil {
        return nil, err
    }
    sort.Ints(data.PassengerIDs)
    return data, nil
}

func parseInputLine(data *caseData, line string) error {
    body, ts, err := splitTimestamp(line, 1)
    if err != nil {
        return err
    }
    switch {
    case strings.Contains(body, "-WEI-"):
        return parsePassengerInput(data, body, ts)
    case strings.HasPrefix(body, "MAINT-"):
        return parseMaintInput(data, body, ts)
    case strings.HasPrefix(body, "UPDATE-"):
        return parseUpdateInput(data, body, ts)
    case strings.HasPrefix(body, "RECYCLE-"):
        return parseRecycleInput(data, body, ts)
    default:
        return fmt.Errorf("unknown input event %q", body)
    }
}

func parsePassengerInput(data *caseData, body string, ts int) error {
    parts := strings.Split(body, "-")
    if len(parts) != 7 || parts[1] != "WEI" || parts[3] != "FROM" || parts[5] != "TO" {
        return fmt.Errorf("bad passenger input %q", body)
    }
    id, err := atoi(parts[0], "passenger id")
    if err != nil {
        return err
    }
    weight, err := atoi(parts[2], "weight")
    if err != nil {
        return err
    }
    from, err := parseFloor(parts[4])
    if err != nil {
        return err
    }
    to, err := parseFloor(parts[6])
    if err != nil {
        return err
    }
    if _, exists := data.Passengers[id]; exists {
        return fmt.Errorf("duplicate passenger id %d", id)
    }
    data.Passengers[id] = &passengerReq{
        ID: id, Weight: weight, From: from, To: to, Time: ts,
    }
    data.PassengerIDs = append(data.PassengerIDs, id)
    return nil
}

func parseMaintInput(data *caseData, body string, ts int) error {
    parts := strings.Split(body, "-")
    if len(parts) != 4 {
        return fmt.Errorf("bad MAINT input %q", body)
    }
    eid, err := atoi(parts[1], "elevator id")
    if err != nil {
        return err
    }
    wid, err := atoi(parts[2], "worker id")
    if err != nil {
        return err
    }
    target, err := parseFloor(parts[3])
    if err != nil {
        return err
    }
    data.MaintsByElevator[eid] = append(data.MaintsByElevator[eid], maintReq{
        ElevatorID: eid, WorkerID: wid, Target: target, Time: ts,
    })
    return nil
}

func parseUpdateInput(data *caseData, body string, ts int) error {
    parts := strings.Split(body, "-")
    if len(parts) != 2 {
        return fmt.Errorf("bad UPDATE input %q", body)
    }
    eid, err := atoi(parts[1], "elevator id")
    if err != nil {
        return err
    }
    data.UpdatesByElevator[eid] = append(data.UpdatesByElevator[eid], updateReq{
        ElevatorID: eid, Time: ts,
    })
    return nil
}

func parseRecycleInput(data *caseData, body string, ts int) error {
    parts := strings.Split(body, "-")
    if len(parts) != 2 {
        return fmt.Errorf("bad RECYCLE input %q", body)
    }
    eid, err := atoi(parts[1], "backup elevator id")
    if err != nil {
        return err
    }
    data.RecyclesByBackup[eid] = append(data.RecyclesByBackup[eid], recycleReq{
        BackupID: eid, Time: ts,
    })
    return nil
}

func newJudge(data *caseData) *judge {
    j := &judge{
        data:         data,
        passengers:   make(map[int]*passengerState),
        workers:      make(map[int]*workerState),
        cars:         make(map[int]*carState),
        shafts:       make(map[int]*shaftState),
        maintIndex:   make(map[int]int),
        updateIndex:  make(map[int]int),
        recycleIndex: make(map[int]int),
    }

    for id, req := range data.Passengers {
        j.passengers[id] = &passengerState{
            OutsideAt: req.From,
        }
    }
    for shaftID := 1; shaftID <= 6; shaftID++ {
        j.shafts[shaftID] = &shaftState{
            ID:          shaftID,
            Mode:        modeNormal,
            ModeHistory: []modeValue{{Time: 0, Value: modeNormal}},
        }
        j.cars[shaftID] = &carState{
            ID:             shaftID,
            Shaft:          shaftID,
            Main:           true,
            Active:         true,
            Floor:          f1,
            OrdinaryIDs:    make(map[int]struct{}),
            WorkerIDs:      make(map[int]struct{}),
            ReceiveHistory: []timeValue{{Time: 0, Value: 0}},
        }
        j.cars[shaftID+6] = &carState{
            ID:             shaftID + 6,
            Shaft:          shaftID,
            Main:           false,
            Active:         false,
            Floor:          f1,
            OrdinaryIDs:    make(map[int]struct{}),
            WorkerIDs:      make(map[int]struct{}),
            ReceiveHistory: []timeValue{{Time: 0, Value: 0}},
        }
    }
    return j
}

func (j *judge) judgeOutput(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        j.lineNo++
        line := strings.TrimSpace(scanner.Text())
        if line == "" {
            continue
        }
        ev, err := parseOutputLine(line)
        if err != nil {
            return fmt.Errorf("line %d: %w", j.lineNo, err)
        }
        if ev.Time < j.lastOutputTime {
            return fmt.Errorf("line %d: timestamp decreases from %.4f to %.4f",
                j.lineNo, seconds(j.lastOutputTime), seconds(ev.Time))
        }
        j.lastOutputTime = ev.Time
        if err := j.handleEvent(ev); err != nil {
            return fmt.Errorf("line %d: %w", j.lineNo, err)
        }
    }
    if err := scanner.Err(); err != nil {
        return err
    }
    return nil
}

func parseOutputLine(line string) (*event, error) {
    body, ts, err := splitTimestamp(line, 4)
    if err != nil {
        return nil, err
    }
    ev := &event{Time: ts, Raw: body}

    switch {
    case strings.HasPrefix(body, "RECEIVE-"):
        parts := strings.Split(body, "-")
        if len(parts) != 3 {
            return nil, fmt.Errorf("bad RECEIVE %q", body)
        }
        ev.Kind = "RECEIVE"
        ev.PersonID, err = atoi(parts[1], "person id")
        if err != nil {
            return nil, err
        }
        ev.Elevator, err = atoi(parts[2], "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "ARRIVE-"), strings.HasPrefix(body, "OPEN-"), strings.HasPrefix(body, "CLOSE-"):
        parts := strings.Split(body, "-")
        if len(parts) != 3 {
            return nil, fmt.Errorf("bad event %q", body)
        }
        ev.Kind = parts[0]
        ev.Floor, err = parseFloor(parts[1])
        if err != nil {
            return nil, err
        }
        ev.Elevator, err = atoi(parts[2], "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "IN-"):
        parts := strings.Split(body, "-")
        if len(parts) != 4 {
            return nil, fmt.Errorf("bad IN %q", body)
        }
        ev.Kind = "IN"
        ev.PersonID, err = atoi(parts[1], "person id")
        if err != nil {
            return nil, err
        }
        ev.Floor, err = parseFloor(parts[2])
        if err != nil {
            return nil, err
        }
        ev.Elevator, err = atoi(parts[3], "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "OUT-S-"), strings.HasPrefix(body, "OUT-F-"):
        parts := strings.Split(body, "-")
        if len(parts) != 5 {
            return nil, fmt.Errorf("bad OUT %q", body)
        }
        ev.Kind = "OUT"
        ev.SubKind = parts[1]
        ev.PersonID, err = atoi(parts[2], "person id")
        if err != nil {
            return nil, err
        }
        ev.Floor, err = parseFloor(parts[3])
        if err != nil {
            return nil, err
        }
        ev.Elevator, err = atoi(parts[4], "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "MAINT-ACCEPT-"):
        parts := strings.Split(body, "-")
        if len(parts) != 5 {
            return nil, fmt.Errorf("bad MAINT-ACCEPT %q", body)
        }
        ev.Kind = "MAINT-ACCEPT"
        ev.Elevator, err = atoi(parts[2], "elevator id")
        if err != nil {
            return nil, err
        }
        ev.WorkerID, err = atoi(parts[3], "worker id")
        if err != nil {
            return nil, err
        }
        ev.Target, err = parseFloor(parts[4])
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "MAINT1-BEGIN-"):
        ev.Kind = "MAINT1-BEGIN"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT1-BEGIN-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "MAINT2-BEGIN-"):
        ev.Kind = "MAINT2-BEGIN"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT2-BEGIN-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "MAINT-END-"):
        ev.Kind = "MAINT-END"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT-END-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "UPDATE-ACCEPT-"):
        ev.Kind = "UPDATE-ACCEPT"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-ACCEPT-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "UPDATE-BEGIN-"):
        ev.Kind = "UPDATE-BEGIN"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-BEGIN-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "UPDATE-END-"):
        ev.Kind = "UPDATE-END"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-END-"), "elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "RECYCLE-ACCEPT-"):
        ev.Kind = "RECYCLE-ACCEPT"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-ACCEPT-"), "backup elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "RECYCLE-BEGIN-"):
        ev.Kind = "RECYCLE-BEGIN"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-BEGIN-"), "backup elevator id")
        if err != nil {
            return nil, err
        }
    case strings.HasPrefix(body, "RECYCLE-END-"):
        ev.Kind = "RECYCLE-END"
        ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-END-"), "backup elevator id")
        if err != nil {
            return nil, err
        }
    default:
        return nil, fmt.Errorf("unknown output event %q", body)
    }
    return ev, nil
}

func (j *judge) handleEvent(ev *event) error {
    switch ev.Kind {
    case "RECEIVE":
        return j.handleReceive(ev)
    case "ARRIVE":
        return j.handleArrive(ev)
    case "OPEN":
        return j.handleOpen(ev)
    case "CLOSE":
        return j.handleClose(ev)
    case "IN":
        return j.handleIn(ev)
    case "OUT":
        return j.handleOut(ev)
    case "MAINT-ACCEPT":
        return j.handleMaintAccept(ev)
    case "MAINT1-BEGIN":
        return j.handleMaint1Begin(ev)
    case "MAINT2-BEGIN":
        return j.handleMaint2Begin(ev)
    case "MAINT-END":
        return j.handleMaintEnd(ev)
    case "UPDATE-ACCEPT":
        return j.handleUpdateAccept(ev)
    case "UPDATE-BEGIN":
        return j.handleUpdateBegin(ev)
    case "UPDATE-END":
        return j.handleUpdateEnd(ev)
    case "RECYCLE-ACCEPT":
        return j.handleRecycleAccept(ev)
    case "RECYCLE-BEGIN":
        return j.handleRecycleBegin(ev)
    case "RECYCLE-END":
        return j.handleRecycleEnd(ev)
    default:
        return fmt.Errorf("unsupported event %s", ev.Kind)
    }
}

func (j *judge) handleReceive(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    req, ok := j.data.Passengers[ev.PersonID]
    if !ok {
        return fmt.Errorf("RECEIVE refers to unknown passenger %d", ev.PersonID)
    }
    ps := j.passengers[ev.PersonID]
    if ps.Finished {
        return fmt.Errorf("passenger %d already finished", ev.PersonID)
    }
    if ps.InsideCar != 0 {
        return fmt.Errorf("passenger %d is still inside elevator %d", ev.PersonID, ps.InsideCar)
    }
    if ps.AssignedCar != 0 {
        return fmt.Errorf("passenger %d already has unfinished RECEIVE on elevator %d", ev.PersonID, ps.AssignedCar)
    }
    if !carCanReceive(shaft.Mode, car.Main) {
        return fmt.Errorf("elevator %d cannot RECEIVE in mode %s", car.ID, modeName(shaft.Mode))
    }
    if !car.Active {
        return fmt.Errorf("elevator %d is inactive", car.ID)
    }
    if ps.OutsideAt == req.To {
        return fmt.Errorf("passenger %d is already at final floor %s", ev.PersonID, floorName(req.To))
    }
    ps.AssignedCar = car.ID
    car.ReceiveCount++
    pushTimeValue(&car.ReceiveHistory, ev.Time, car.ReceiveCount)
    return nil
}

func (j *judge) handleArrive(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
        return err
    }
    if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
        return err
    }
    if car.DoorOpen {
        return fmt.Errorf("elevator %d moves with door open", car.ID)
    }
    prevFloor := car.Floor
    if absInt(int(ev.Floor-prevFloor)) != 1 {
        return fmt.Errorf("elevator %d moves from %s to %s without exactly one-floor ARRIVE",
            car.ID, floorName(prevFloor), floorName(ev.Floor))
    }
    speed := j.moveDuration(car, shaft)
    start := ev.Time - speed
    if start < car.LastPhysicalTime {
        return fmt.Errorf("elevator %d violates move timing: move starts at %.4f before previous physical action at %.4f",
            car.ID, seconds(start), seconds(car.LastPhysicalTime))
    }
    if !j.moveAllowedAtStart(car, prevFloor, ev.Floor, start) {
        return fmt.Errorf("elevator %d starts moving toward %s at %.4f without active RECEIVE or valid command",
            car.ID, floorName(ev.Floor), seconds(start))
    }
    car.Floor = ev.Floor
    car.LastPhysicalTime = ev.Time
    j.arriveCount++
    if err := j.validateArriveByMode(car, shaft, prevFloor, ev.Floor); err != nil {
        return err
    }
    return j.checkShaftOrdering(car.Shaft)
}

func (j *judge) handleOpen(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
        return err
    }
    if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
        return err
    }
    if car.DoorOpen {
        return fmt.Errorf("elevator %d opens while already open", car.ID)
    }
    if car.Floor != ev.Floor {
        return fmt.Errorf("OPEN floor mismatch: elevator %d at %s but outputs OPEN-%s",
            car.ID, floorName(car.Floor), floorName(ev.Floor))
    }
    if ev.Time < car.LastPhysicalTime {
        return fmt.Errorf("elevator %d opens before previous action finishes", car.ID)
    }
    if err := j.validateOpenByMode(car, shaft); err != nil {
        return err
    }
    car.DoorOpen = true
    car.LastOpenTime = ev.Time
    car.LastPhysicalTime = ev.Time
    j.openCount++
    return nil
}

func (j *judge) handleClose(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
        return err
    }
    if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
        return err
    }
    if !car.DoorOpen {
        return fmt.Errorf("elevator %d closes while already closed", car.ID)
    }
    if car.Floor != ev.Floor {
        return fmt.Errorf("CLOSE floor mismatch: elevator %d at %s but outputs CLOSE-%s",
            car.ID, floorName(car.Floor), floorName(ev.Floor))
    }
    if ev.Time-car.LastOpenTime < doorGap {
        return fmt.Errorf("elevator %d closes too early: open at %.4f, close at %.4f",
            car.ID, seconds(car.LastOpenTime), seconds(ev.Time))
    }
    if car.OrdinaryLoad > maxLoad {
        return fmt.Errorf("elevator %d closes with overload %dkg", car.ID, car.OrdinaryLoad)
    }
    if err := j.validateCloseByMode(car, shaft); err != nil {
        return err
    }
    car.DoorOpen = false
    car.LastPhysicalTime = ev.Time
    j.closeCount++
    return nil
}

func (j *judge) handleIn(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
        return err
    }
    if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
        return err
    }
    if !car.DoorOpen {
        return fmt.Errorf("person %d enters elevator %d while door is closed", ev.PersonID, car.ID)
    }
    if car.Floor != ev.Floor {
        return fmt.Errorf("IN floor mismatch: elevator %d at %s but outputs IN at %s",
            car.ID, floorName(car.Floor), floorName(ev.Floor))
    }
    if req, ok := j.data.Passengers[ev.PersonID]; ok {
        if shaft.Mode == modeTest {
            return fmt.Errorf("ordinary passenger %d enters during TEST", ev.PersonID)
        }
        return j.handleOrdinaryIn(req, car, ev.Time)
    }
    return j.handleWorkerIn(ev.PersonID, car, shaft, ev.Time)
}

func (j *judge) handleOut(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
        return err
    }
    if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
        return err
    }
    if !car.DoorOpen {
        return fmt.Errorf("person %d leaves elevator %d while door is closed", ev.PersonID, car.ID)
    }
    if car.Floor != ev.Floor {
        return fmt.Errorf("OUT floor mismatch: elevator %d at %s but outputs OUT at %s",
            car.ID, floorName(car.Floor), floorName(ev.Floor))
    }
    if req, ok := j.data.Passengers[ev.PersonID]; ok {
        if shaft.Mode == modeTest {
            return fmt.Errorf("ordinary passenger %d leaves during TEST", ev.PersonID)
        }
        return j.handleOrdinaryOut(req, car, ev.SubKind, ev.Time)
    }
    return j.handleWorkerOut(ev.PersonID, car, shaft, ev.SubKind, ev.Time)
}

func (j *judge) handleOrdinaryIn(req *passengerReq, car *carState, ts int) error {
    ps := j.passengers[req.ID]
    if ps.Finished {
        return fmt.Errorf("passenger %d already finished", req.ID)
    }
    if ps.AssignedCar != car.ID {
        return fmt.Errorf("passenger %d enters elevator %d without active RECEIVE on it", req.ID, car.ID)
    }
    if ps.InsideCar != 0 {
        return fmt.Errorf("passenger %d already inside elevator %d", req.ID, ps.InsideCar)
    }
    if ps.OutsideAt != car.Floor {
        return fmt.Errorf("passenger %d is at %s but enters elevator %d at %s",
            req.ID, floorName(ps.OutsideAt), car.ID, floorName(car.Floor))
    }
    ps.InsideCar = car.ID
    car.OrdinaryIDs[req.ID] = struct{}{}
    car.OrdinaryLoad += req.Weight
    car.LastPhysicalTime = ts
    return nil
}

func (j *judge) handleOrdinaryOut(req *passengerReq, car *carState, sub string, ts int) error {
    ps := j.passengers[req.ID]
    if ps.InsideCar != car.ID {
        return fmt.Errorf("passenger %d leaves elevator %d without being inside it", req.ID, car.ID)
    }
    if _, ok := car.OrdinaryIDs[req.ID]; !ok {
        return fmt.Errorf("passenger %d is missing from elevator %d manifest", req.ID, car.ID)
    }
    if sub == "S" && car.Floor != req.To {
        return fmt.Errorf("passenger %d uses OUT-S at %s but final destination is %s",
            req.ID, floorName(car.Floor), floorName(req.To))
    }
    if sub == "F" && car.Floor == req.To {
        return fmt.Errorf("passenger %d uses OUT-F at final floor %s", req.ID, floorName(car.Floor))
    }
    delete(car.OrdinaryIDs, req.ID)
    car.OrdinaryLoad -= req.Weight
    ps.InsideCar = 0
    ps.AssignedCar = 0
    ps.OutsideAt = car.Floor
    if sub == "S" {
        ps.Finished = true
        ps.FinalTime = ts
    }
    j.dropReceive(car, ts)
    car.LastPhysicalTime = ts
    return nil
}

func (j *judge) handleWorkerIn(workerID int, car *carState, shaft *shaftState, ts int) error {
    if !car.Main {
        return fmt.Errorf("worker %d can only enter main elevator", workerID)
    }
    if shaft.Mode != modeRepAccept || shaft.Maint == nil {
        return fmt.Errorf("worker %d enters outside REP_ACCEPT", workerID)
    }
    if shaft.Maint.WorkerID != workerID {
        return fmt.Errorf("worker %d does not match current MAINT worker %d", workerID, shaft.Maint.WorkerID)
    }
    if car.Floor != f1 {
        return fmt.Errorf("worker %d must enter at F1", workerID)
    }
    if len(car.OrdinaryIDs) != 0 {
        return fmt.Errorf("worker %d enters while ordinary passengers remain in elevator %d", workerID, car.ID)
    }
    ws := j.workers[workerID]
    if ws == nil {
        ws = &workerState{OutsideAt: f1}
        j.workers[workerID] = ws
    }
    if ws.InCar != 0 {
        return fmt.Errorf("worker %d is already inside elevator %d", workerID, ws.InCar)
    }
    if ws.Finished {
        return fmt.Errorf("worker %d already finished", workerID)
    }
    ws.InCar = car.ID
    ws.OutsideAt = f1
    car.WorkerIDs[workerID] = struct{}{}
    shaft.Maint.Phase = "worker-boarded"
    car.LastPhysicalTime = ts
    return nil
}

func (j *judge) handleWorkerOut(workerID int, car *carState, shaft *shaftState, sub string, ts int) error {
    if sub != "S" {
        return fmt.Errorf("worker %d cannot use OUT-F", workerID)
    }
    if !car.Main || shaft.Mode != modeTest || shaft.Maint == nil {
        return fmt.Errorf("worker %d leaves outside TEST", workerID)
    }
    if shaft.Maint.WorkerID != workerID {
        return fmt.Errorf("worker %d does not match active maintenance worker %d", workerID, shaft.Maint.WorkerID)
    }
    if car.Floor != f1 {
        return fmt.Errorf("worker %d must leave at F1", workerID)
    }
    if _, ok := car.WorkerIDs[workerID]; !ok {
        return fmt.Errorf("worker %d is not inside elevator %d", workerID, car.ID)
    }
    ws := j.workers[workerID]
    if ws == nil || ws.InCar != car.ID {
        return fmt.Errorf("worker %d runtime state mismatch", workerID)
    }
    delete(car.WorkerIDs, workerID)
    ws.InCar = 0
    ws.OutsideAt = f1
    ws.Finished = true
    shaft.Maint.Phase = "worker-out"
    car.LastPhysicalTime = ts
    return nil
}

func (j *judge) handleMaintAccept(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main {
        return fmt.Errorf("MAINT-ACCEPT must target main elevator, got %d", car.ID)
    }
    if shaft.Mode != modeNormal {
        return fmt.Errorf("MAINT-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
    }
    req, err := j.nextMaint(ev.Elevator, ev.WorkerID, ev.Target, ev.Time)
    if err != nil {
        return err
    }
    shaft.Maint = &maintRun{
        AcceptAt: ev.Time,
        WorkerID: req.WorkerID,
        Target:   req.Target,
        Phase:    "accept",
    }
    j.workers[req.WorkerID] = &workerState{OutsideAt: f1}
    j.setMode(shaft, modeRepAccept, ev.Time)
    return nil
}

func (j *judge) handleMaint1Begin(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main || shaft.Mode != modeRepAccept || shaft.Maint == nil {
        return fmt.Errorf("MAINT1-BEGIN on elevator %d outside REP_ACCEPT", ev.Elevator)
    }
    if car.Floor != f1 || car.DoorOpen {
        return fmt.Errorf("MAINT1-BEGIN requires closed F1 state on elevator %d", ev.Elevator)
    }
    if len(car.OrdinaryIDs) != 0 {
        return fmt.Errorf("MAINT1-BEGIN requires no ordinary passengers in elevator %d", ev.Elevator)
    }
    if len(car.WorkerIDs) != 1 {
        return fmt.Errorf("MAINT1-BEGIN requires exactly one worker in elevator %d", ev.Elevator)
    }
    if shaft.Maint.Phase != "closed-after-worker" {
        return fmt.Errorf("MAINT1-BEGIN must happen after worker boards and elevator closes")
    }
    j.endAllReceivesOnCar(car, ev.Time)
    shaft.Maint.BeginAt = ev.Time
    shaft.Maint.Phase = "repair"
    j.setMode(shaft, modeRepair, ev.Time)
    car.LastPhysicalTime = ev.Time
    return nil
}

func (j *judge) handleMaint2Begin(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main || shaft.Mode != modeRepair || shaft.Maint == nil {
        return fmt.Errorf("MAINT2-BEGIN on elevator %d outside REPAIR", ev.Elevator)
    }
    if ev.Time-shaft.Maint.BeginAt < stopGap {
        return fmt.Errorf("MAINT2-BEGIN on elevator %d happens before 1s repair delay", ev.Elevator)
    }
    if car.Floor != f1 || car.DoorOpen {
        return fmt.Errorf("MAINT2-BEGIN requires elevator %d to stay closed at F1", ev.Elevator)
    }
    if len(car.WorkerIDs) != 1 {
        return fmt.Errorf("MAINT2-BEGIN requires worker to stay inside elevator %d", ev.Elevator)
    }
    shaft.Maint.Phase = "to-target"
    j.setMode(shaft, modeTest, ev.Time)
    car.LastPhysicalTime = ev.Time
    return nil
}

func (j *judge) handleMaintEnd(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main || shaft.Mode != modeTest || shaft.Maint == nil {
        return fmt.Errorf("MAINT-END on elevator %d outside TEST", ev.Elevator)
    }
    if ev.Time-shaft.Maint.AcceptAt > maintLimit {
        return fmt.Errorf("MAINT response on elevator %d exceeds 7s", ev.Elevator)
    }
    if car.Floor != f1 || car.DoorOpen {
        return fmt.Errorf("MAINT-END requires elevator %d to be closed at F1", ev.Elevator)
    }
    if shaft.Maint.Phase != "closed-after-worker-out" {
        return fmt.Errorf("MAINT-END requires worker to exit and door to close first")
    }
    shaft.Maint = nil
    j.setMode(shaft, modeNormal, ev.Time)
    car.LastPhysicalTime = ev.Time
    return nil
}

func (j *judge) handleUpdateAccept(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main {
        return fmt.Errorf("UPDATE-ACCEPT must target a main elevator")
    }
    if shaft.Mode != modeNormal {
        return fmt.Errorf("UPDATE-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
    }
    if _, err := j.nextUpdate(ev.Elevator, ev.Time); err != nil {
        return err
    }
    shaft.Update = &updateRun{AcceptAt: ev.Time}
    j.setMode(shaft, modeUpAccept, ev.Time)
    return nil
}

func (j *judge) handleUpdateBegin(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main || shaft.Mode != modeUpAccept || shaft.Update == nil {
        return fmt.Errorf("UPDATE-BEGIN on elevator %d outside UP_ACCEPT", ev.Elevator)
    }
    if car.Floor != f3 || car.DoorOpen {
        return fmt.Errorf("UPDATE-BEGIN requires main elevator %d to be closed at F3", ev.Elevator)
    }
    if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
        return fmt.Errorf("UPDATE-BEGIN requires main elevator %d to be empty", ev.Elevator)
    }
    j.endAllReceivesOnCar(car, ev.Time)
    shaft.Update.BeginAt = ev.Time
    j.setMode(shaft, modeUpdate, ev.Time)
    car.LastPhysicalTime = ev.Time
    return nil
}

func (j *judge) handleUpdateEnd(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if !car.Main || shaft.Mode != modeUpdate || shaft.Update == nil {
        return fmt.Errorf("UPDATE-END on elevator %d outside UPDATE", ev.Elevator)
    }
    if ev.Time-shaft.Update.BeginAt < stopGap {
        return fmt.Errorf("UPDATE-END on elevator %d happens before 1s update delay", ev.Elevator)
    }
    if ev.Time-shaft.Update.AcceptAt > updateLimit {
        return fmt.Errorf("UPDATE response on elevator %d exceeds 6s", ev.Elevator)
    }
    if car.Floor != f3 || car.DoorOpen {
        return fmt.Errorf("UPDATE-END requires main elevator %d to stay closed at F3", ev.Elevator)
    }
    backup := j.cars[shaft.ID+6]
    backup.Active = true
    backup.Floor = f1
    backup.DoorOpen = false
    backup.LastPhysicalTime = ev.Time
    j.setMode(shaft, modeDouble, ev.Time)
    shaft.Update = nil
    car.LastPhysicalTime = ev.Time
    return j.checkShaftOrdering(shaft.ID)
}

func (j *judge) handleRecycleAccept(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if car.Main {
        return fmt.Errorf("RECYCLE-ACCEPT must target backup elevator")
    }
    if shaft.Mode != modeDouble {
        return fmt.Errorf("RECYCLE-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
    }
    if _, err := j.nextRecycle(ev.Elevator, ev.Time); err != nil {
        return err
    }
    shaft.Recycle = &recycleRun{AcceptAt: ev.Time}
    j.setMode(shaft, modeRecAccept, ev.Time)
    return nil
}

func (j *judge) handleRecycleBegin(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if car.Main || shaft.Mode != modeRecAccept || shaft.Recycle == nil {
        return fmt.Errorf("RECYCLE-BEGIN on elevator %d outside REC_ACCEPT", ev.Elevator)
    }
    if car.Floor != f1 || car.DoorOpen {
        return fmt.Errorf("RECYCLE-BEGIN requires backup elevator %d to be closed at F1", ev.Elevator)
    }
    if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
        return fmt.Errorf("RECYCLE-BEGIN requires backup elevator %d to be empty", ev.Elevator)
    }
    j.endAllReceivesOnCar(car, ev.Time)
    shaft.Recycle.BeginAt = ev.Time
    j.setMode(shaft, modeRecycle, ev.Time)
    car.LastPhysicalTime = ev.Time
    return nil
}

func (j *judge) handleRecycleEnd(ev *event) error {
    car, shaft, err := j.lookupCar(ev.Elevator)
    if err != nil {
        return err
    }
    if car.Main || shaft.Mode != modeRecycle || shaft.Recycle == nil {
        return fmt.Errorf("RECYCLE-END on elevator %d outside RECYCLE", ev.Elevator)
    }
    if ev.Time-shaft.Recycle.BeginAt < stopGap {
        return fmt.Errorf("RECYCLE-END on elevator %d happens before 1s recycle delay", ev.Elevator)
    }
    if ev.Time-shaft.Recycle.AcceptAt > recycleLimit {
        return fmt.Errorf("RECYCLE response on elevator %d exceeds 6s", ev.Elevator)
    }
    if car.Floor != f1 || car.DoorOpen {
        return fmt.Errorf("RECYCLE-END requires backup elevator %d to be closed at F1", ev.Elevator)
    }
    car.Active = false
    car.Floor = f1
    car.DoorOpen = false
    car.LastPhysicalTime = ev.Time
    shaft.Recycle = nil
    j.setMode(shaft, modeNormal, ev.Time)
    return nil
}

func (j *judge) ensureCarActiveAndNotSilent(car *carState, shaft *shaftState, action string) error {
    if !car.Active {
        return fmt.Errorf("elevator %d is inactive but outputs %s", car.ID, action)
    }
    switch shaft.Mode {
    case modeRepair:
        return fmt.Errorf("elevator %d is silent during REPAIR but outputs %s", car.ID, action)
    case modeUpdate:
        return fmt.Errorf("shaft %d is silent during UPDATE but elevator %d outputs %s", shaft.ID, car.ID, action)
    case modeRecycle:
        if !car.Main {
            return fmt.Errorf("backup elevator %d is silent during RECYCLE but outputs %s", car.ID, action)
        }
    }
    return nil
}

func (j *judge) ensureRepAcceptWorkerLock(car *carState, shaft *shaftState, action string) error {
    if !car.Main || shaft.Mode != modeRepAccept || shaft.Maint == nil {
        return nil
    }
    switch shaft.Maint.Phase {
    case "worker-boarded":
        if action != "CLOSE" {
            return fmt.Errorf("after worker boards, only CLOSE is allowed before MAINT1-BEGIN")
        }
    case "closed-after-worker":
        if action != "MAINT1-BEGIN" {
            return fmt.Errorf("after worker boards and door closes, only MAINT1-BEGIN is allowed")
        }
    }
    return nil
}

func (j *judge) validateArriveByMode(car *carState, shaft *shaftState, prev, next floor) error {
    if shaft.Mode == modeTest {
        return j.validateTestArrive(shaft, prev, next)
    }
    return j.ensureRangeByMode(car, shaft.Mode)
}

func (j *judge) validateTestArrive(shaft *shaftState, prev, next floor) error {
    if shaft.Maint == nil {
        return errors.New("missing maintenance context in TEST")
    }
    switch shaft.Maint.Phase {
    case "to-target":
        want := stepToward(prev, shaft.Maint.Target)
        if next != want {
            return fmt.Errorf("TEST route should move from %s to %s, but arrives at %s",
                floorName(prev), floorName(want), floorName(next))
        }
        if next == shaft.Maint.Target {
            shaft.Maint.Phase = "to-f1"
        }
    case "to-f1":
        want := stepToward(prev, f1)
        if next != want {
            return fmt.Errorf("TEST return route should move from %s to %s, but arrives at %s",
                floorName(prev), floorName(want), floorName(next))
        }
    default:
        return fmt.Errorf("TEST forbids ARRIVE while maintenance phase is %s", shaft.Maint.Phase)
    }
    return nil
}

func (j *judge) validateOpenByMode(car *carState, shaft *shaftState) error {
    if shaft.Mode == modeTest {
        if !car.Main || car.Floor != f1 || shaft.Maint == nil || shaft.Maint.Phase != "to-f1" {
            return fmt.Errorf("during TEST only one OPEN-F1 is allowed after returning from target floor")
        }
        shaft.Maint.Phase = "worker-exit-open"
        return nil
    }
    return j.ensureRangeByMode(car, shaft.Mode)
}

func (j *judge) validateCloseByMode(car *carState, shaft *shaftState) error {
    if shaft.Mode == modeRepAccept && car.Main && shaft.Maint != nil && shaft.Maint.Phase == "worker-boarded" {
        if car.Floor != f1 {
            return fmt.Errorf("after worker boards, elevator %d must close at F1", car.ID)
        }
        shaft.Maint.Phase = "closed-after-worker"
    }
    if shaft.Mode == modeTest {
        if !car.Main || car.Floor != f1 || shaft.Maint == nil || shaft.Maint.Phase != "worker-out" {
            return fmt.Errorf("during TEST CLOSE is only allowed after worker exits at F1")
        }
        shaft.Maint.Phase = "closed-after-worker-out"
    }
    return nil
}

func (j *judge) ensureRangeByMode(car *carState, m mode) error {
    switch m {
    case modeDouble, modeRecAccept, modeRecycle:
        return j.ensureDoubleRange(car)
    default:
        return j.ensureNormalRange(car)
    }
}

func (j *judge) ensureNormalRange(car *carState) error {
    if car.Floor < b4 || car.Floor > f7 {
        return fmt.Errorf("elevator %d leaves building range at %s", car.ID, floorName(car.Floor))
    }
    return nil
}

func (j *judge) ensureDoubleRange(car *carState) error {
    if car.Main {
        if car.Floor < f2 || car.Floor > f7 {
            return fmt.Errorf("main elevator %d leaves F2-F7 range at %s", car.ID, floorName(car.Floor))
        }
        return nil
    }
    if car.Floor < b4 || car.Floor > f2 {
        return fmt.Errorf("backup elevator %d leaves B4-F2 range at %s", car.ID, floorName(car.Floor))
    }
    return nil
}

func (j *judge) moveDuration(car *carState, shaft *shaftState) int {
    if car.Main && shaft.Mode == modeTest {
        return moveTest
    }
    return moveNormal
}

func (j *judge) moveAllowedAtStart(car *carState, from, to floor, start int) bool {
    shaft := j.shafts[car.Shaft]
    m := modeAt(shaft.ModeHistory, start)
    if valueAt(car.ReceiveHistory, start) > 0 {
        return true
    }
    if hasCommandMovePrivilege(m, car.Main) {
        return true
    }
    if (m == modeDouble || m == modeRecAccept || m == modeRecycle) && from == f2 && absInt(int(to-from)) == 1 {
        return true
    }
    return false
}

func (j *judge) checkShaftOrdering(shaftID int) error {
    shaft := j.shafts[shaftID]
    if shaft.Mode != modeDouble && shaft.Mode != modeRecAccept && shaft.Mode != modeRecycle {
        return nil
    }
    mainCar := j.cars[shaftID]
    backup := j.cars[shaftID+6]
    if !mainCar.Active || !backup.Active {
        return nil
    }
    if !(mainCar.Floor > backup.Floor) {
        return fmt.Errorf("shaft %d has invalid double-car positions: main at %s, backup at %s",
            shaftID, floorName(mainCar.Floor), floorName(backup.Floor))
    }
    return nil
}

func (j *judge) lookupCar(id int) (*carState, *shaftState, error) {
    car := j.cars[id]
    if car == nil {
        return nil, nil, fmt.Errorf("unknown elevator %d", id)
    }
    return car, j.shafts[car.Shaft], nil
}

func (j *judge) nextMaint(eid, wid int, target floor, ts int) (*maintReq, error) {
    list := j.data.MaintsByElevator[eid]
    idx := j.maintIndex[eid]
    if idx >= len(list) {
        return nil, fmt.Errorf("unexpected MAINT-ACCEPT for elevator %d", eid)
    }
    req := list[idx]
    if req.WorkerID != wid || req.Target != target {
        return nil, fmt.Errorf("MAINT-ACCEPT does not match input for elevator %d", eid)
    }
    j.maintIndex[eid] = idx + 1
    return &req, nil
}

func (j *judge) nextUpdate(eid int, ts int) (*updateReq, error) {
    list := j.data.UpdatesByElevator[eid]
    idx := j.updateIndex[eid]
    if idx >= len(list) {
        return nil, fmt.Errorf("unexpected UPDATE-ACCEPT for elevator %d", eid)
    }
    req := list[idx]
    j.updateIndex[eid] = idx + 1
    return &req, nil
}

func (j *judge) nextRecycle(eid int, ts int) (*recycleReq, error) {
    list := j.data.RecyclesByBackup[eid]
    idx := j.recycleIndex[eid]
    if idx >= len(list) {
        return nil, fmt.Errorf("unexpected RECYCLE-ACCEPT for elevator %d", eid)
    }
    req := list[idx]
    j.recycleIndex[eid] = idx + 1
    return &req, nil
}

func (j *judge) endAllReceivesOnCar(car *carState, ts int) {
    if car.ReceiveCount == 0 {
        return
    }
    for _, ps := range j.passengers {
        if ps.AssignedCar == car.ID {
            ps.AssignedCar = 0
        }
    }
    car.ReceiveCount = 0
    pushTimeValue(&car.ReceiveHistory, ts, 0)
}

func (j *judge) dropReceive(car *carState, ts int) {
    if car.ReceiveCount > 0 {
        car.ReceiveCount--
    }
    pushTimeValue(&car.ReceiveHistory, ts, car.ReceiveCount)
}

func (j *judge) setMode(shaft *shaftState, m mode, ts int) {
    shaft.Mode = m
    pushModeValue(&shaft.ModeHistory, ts, m)
}

func (j *judge) finish(realTime, cpuTime, tmax float64) (*metrics, error) {
    for _, id := range j.data.PassengerIDs {
        ps := j.passengers[id]
        if !ps.Finished {
            return nil, fmt.Errorf("passenger %d does not reach final destination", id)
        }
        if ps.AssignedCar != 0 || ps.InsideCar != 0 {
            return nil, fmt.Errorf("passenger %d still has unfinished runtime state", id)
        }
    }
    for id, ws := range j.workers {
        if ws.InCar != 0 {
            return nil, fmt.Errorf("worker %d is still inside elevator %d", id, ws.InCar)
        }
    }
    for id, car := range j.cars {
        if car.DoorOpen {
            return nil, fmt.Errorf("elevator %d ends with door open", id)
        }
        if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
            return nil, fmt.Errorf("elevator %d still contains passengers", id)
        }
        if car.ReceiveCount != 0 {
            return nil, fmt.Errorf("elevator %d still has unfinished RECEIVE count %d", id, car.ReceiveCount)
        }
    }
    for shaftID, shaft := range j.shafts {
        if shaft.Mode != modeNormal {
            return nil, fmt.Errorf("shaft %d does not return to NORMAL, current mode %s", shaftID, modeName(shaft.Mode))
        }
    }
    if err := j.checkAcceptConsumption(); err != nil {
        return nil, err
    }
    tavg, err := j.computeTavg()
    if err != nil {
        return nil, err
    }
    w := float64(j.arriveCount)*0.4 + float64(j.openCount+j.closeCount)*0.1
    trun := seconds(j.lastOutputTime)
    if realTime >= 0 && realTime > trun {
        trun = realTime
    }
    return &metrics{Trun: trun, Tavg: tavg, W: w}, nil
}

func (j *judge) checkAcceptConsumption() error {
    for eid, list := range j.data.MaintsByElevator {
        if j.maintIndex[eid] != len(list) {
            return fmt.Errorf("missing MAINT-ACCEPT for elevator %d", eid)
        }
    }
    for eid, list := range j.data.UpdatesByElevator {
        if j.updateIndex[eid] != len(list) {
            return fmt.Errorf("missing UPDATE-ACCEPT for elevator %d", eid)
        }
    }
    for eid, list := range j.data.RecyclesByBackup {
        if j.recycleIndex[eid] != len(list) {
            return fmt.Errorf("missing RECYCLE-ACCEPT for elevator %d", eid)
        }
    }
    return nil
}

func (j *judge) computeTavg() (float64, error) {
    if len(j.data.PassengerIDs) == 0 {
        return 0, nil
    }
    total := 0
    for _, id := range j.data.PassengerIDs {
        req := j.data.Passengers[id]
        ps := j.passengers[id]
        if !ps.Finished {
            return 0, fmt.Errorf("passenger %d does not finish", id)
        }
        total += ps.FinalTime - req.Time
    }
    return float64(total) / float64(len(j.data.PassengerIDs)) / tick, nil
}

func splitTimestamp(line string, maxDigits int) (string, int, error) {
    left := strings.IndexByte(line, '[')
    right := strings.IndexByte(line, ']')
    if left != 0 || right < 0 || right+1 >= len(line) {
        return "", 0, fmt.Errorf("bad line format %q", line)
    }
    ts, err := parseTime(line[1:right], maxDigits)
    if err != nil {
        return "", 0, err
    }
    return strings.TrimSpace(line[right+1:]), ts, nil
}

func parseTime(raw string, maxDigits int) (int, error) {
    s := strings.TrimSpace(raw)
    if s == "" {
        return 0, errors.New("empty timestamp")
    }
    parts := strings.Split(s, ".")
    if len(parts) == 1 {
        parts = append(parts, "")
    }
    if len(parts) != 2 {
        return 0, fmt.Errorf("bad timestamp %q", raw)
    }
    intPart, err := strconv.Atoi(parts[0])
    if err != nil {
        return 0, fmt.Errorf("bad timestamp %q", raw)
    }
    frac := parts[1]
    if len(frac) > maxDigits {
        return 0, fmt.Errorf("timestamp %q has too many decimal digits", raw)
    }
    if len(frac) > 4 {
        return 0, fmt.Errorf("timestamp %q has too many decimal digits", raw)
    }
    for len(frac) < 4 {
        frac += "0"
    }
    fracPart, err := strconv.Atoi(frac)
    if err != nil {
        return 0, fmt.Errorf("bad timestamp %q", raw)
    }
    return intPart*tick + fracPart, nil
}

func parseFloor(name string) (floor, error) {
    f, ok := floorIndex[name]
    if !ok {
        return 0, fmt.Errorf("bad floor %q", name)
    }
    return f, nil
}

func carCanReceive(m mode, main bool) bool {
    switch m {
    case modeNormal, modeRepAccept, modeUpAccept:
        return main
    case modeDouble, modeRecAccept:
        return true
    case modeRecycle:
        return main
    default:
        return false
    }
}

func hasCommandMovePrivilege(m mode, main bool) bool {
    switch m {
    case modeRepAccept, modeRepair, modeTest, modeUpAccept:
        return main
    case modeRecAccept:
        return !main
    default:
        return false
    }
}

func modeAt(history []modeValue, ts int) mode {
    cur := history[0].Value
    for _, item := range history {
        if item.Time > ts {
            break
        }
        cur = item.Value
    }
    return cur
}

func valueAt(history []timeValue, ts int) int {
    cur := history[0].Value
    for _, item := range history {
        if item.Time > ts {
            break
        }
        cur = item.Value
    }
    return cur
}

func pushTimeValue(history *[]timeValue, ts, value int) {
    h := *history
    if len(h) > 0 && h[len(h)-1].Time == ts {
        h[len(h)-1].Value = value
        *history = h
        return
    }
    *history = append(h, timeValue{Time: ts, Value: value})
}

func pushModeValue(history *[]modeValue, ts int, value mode) {
    h := *history
    if len(h) > 0 && h[len(h)-1].Time == ts {
        h[len(h)-1].Value = value
        *history = h
        return
    }
    *history = append(h, modeValue{Time: ts, Value: value})
}

func stepToward(cur, target floor) floor {
    switch {
    case cur < target:
        return cur + 1
    case cur > target:
        return cur - 1
    default:
        return cur
    }
}

func seconds(v int) float64 {
    return float64(v) / tick
}

func floorName(f floor) string {
    return floorNames[int(f)]
}

func modeName(m mode) string {
    switch m {
    case modeNormal:
        return "NORMAL"
    case modeRepAccept:
        return "REP_ACCEPT"
    case modeRepair:
        return "REPAIR"
    case modeTest:
        return "TEST"
    case modeUpAccept:
        return "UP_ACCEPT"
    case modeUpdate:
        return "UPDATE"
    case modeDouble:
        return "DOUBLE"
    case modeRecAccept:
        return "REC_ACCEPT"
    case modeRecycle:
        return "RECYCLE"
    default:
        return "UNKNOWN"
    }
}

func atoi(raw, name string) (int, error) {
    v, err := strconv.Atoi(raw)
    if err != nil {
        return 0, fmt.Errorf("bad %s %q", name, raw)
    }
    return v, nil
}

func absInt(v int) int {
    if v < 0 {
        return -v
    }
    return v
}

func failf(format string, args ...any) {
    fmt.Fprintf(os.Stderr, format+"\n", args...)
    os.Exit(1)
}
...全文
89 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

304

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧