From b4ce31bacfdf785b54df6fd543a5f9820b00f4da Mon Sep 17 00:00:00 2001 From: Clumsy Penguin <16216763+darkenvironment@user.noreply.gitee.com> Date: Sun, 3 May 2026 09:49:55 +0800 Subject: [PATCH 1/3] qq --- config.py | 70 ++++++++ main.py | 439 ++++++++++++++++++++++++++++++++++++++++++++++ scheduler.py | 187 ++++++++++++++++++++ scoring.py | 154 ++++++++++++++++ switch_control.py | 88 ++++++++++ time_system.py | 147 ++++++++++++++++ track.py | 203 +++++++++++++++++++++ train.py | 188 ++++++++++++++++++++ 8 files changed, 1476 insertions(+) create mode 100644 config.py create mode 100644 main.py create mode 100644 scheduler.py create mode 100644 scoring.py create mode 100644 switch_control.py create mode 100644 time_system.py create mode 100644 track.py create mode 100644 train.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..ff5c25d --- /dev/null +++ b/config.py @@ -0,0 +1,70 @@ +SCREEN_WIDTH = 1200 +SCREEN_HEIGHT = 800 +FPS = 60 + +COLORS = { + 'WHITE': (255, 255, 255), + 'BLACK': (0, 0, 0), + 'RED': (255, 0, 0), + 'GREEN': (0, 255, 0), + 'BLUE': (0, 0, 255), + 'YELLOW': (255, 255, 0), + 'GRAY': (128, 128, 128), + 'BROWN': (139, 69, 19), + 'LIGHT_GRAY': (200, 200, 200), + 'DARK_GRAY': (64, 64, 64), + 'ORANGE': (255, 165, 0), + 'PURPLE': (128, 0, 128), + 'CYAN': (0, 255, 255) +} + +TRAIN_COLORS = [ + COLORS['RED'], + COLORS['GREEN'], + COLORS['BLUE'], + COLORS['YELLOW'], + COLORS['ORANGE'], + COLORS['PURPLE'], + COLORS['CYAN'] +] + +TRAIN_SPEED = { + 'MIN': 0.5, + 'DEFAULT': 2.0, + 'MAX': 5.0 +} + +TRAIN_LENGTH = 40 +TRAIN_WIDTH = 10 + +TRACK_WIDTH = 6 +SWITCH_RADIUS = 8 +STATION_RADIUS = 12 + +GAME_TIME_MULTIPLIER = 1.0 +DAY_LENGTH_SECONDS = 300 + +SCHEDULE_PUNCTUALITY = { + 'ON_TIME': 0.95, + 'DELAYED': 0.8, + 'LATE': 0.6, + 'CRITICALLY_LATE': 0.3 +} + +SCORING = { + 'ON_TIME_ARRIVAL': 100, + 'DELAYED_ARRIVAL': 50, + 'LATE_ARRIVAL': 20, + 'CRITICALLY_LATE_ARRIVAL': -10, + 'COLLISION': -500, + 'EFFICIENT_SWITCH': 10, + 'TRAIN_COMPLETED': 200 +} + +UI_PANEL_WIDTH = 250 +UI_PADDING = 10 +UI_FONT_SIZE = 20 +UI_SMALL_FONT_SIZE = 16 + +COLLISION_DISTANCE = 30 +SAFE_DISTANCE = 60 diff --git a/main.py b/main.py new file mode 100644 index 0000000..5f3209c --- /dev/null +++ b/main.py @@ -0,0 +1,439 @@ +import pygame +import sys +import math + +from config import ( + SCREEN_WIDTH, SCREEN_HEIGHT, FPS, COLORS, UI_PANEL_WIDTH, + UI_PADDING, UI_FONT_SIZE, UI_SMALL_FONT_SIZE, TRAIN_SPEED +) +from track import TrackNetwork +from train import Train +from switch_control import SwitchController +from scheduler import TrainScheduler +from time_system import TimeSystem, ScheduleSystem +from scoring import ScoringSystem + + +class TrainSchedulerGame: + def __init__(self): + pygame.init() + pygame.font.init() + + self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + pygame.display.set_caption('火车调度大师') + + self.clock = pygame.time.Clock() + self.running = True + self.is_paused = False + self.game_over = False + self.show_debug = False + + self.font = pygame.font.Font(None, UI_FONT_SIZE) + self.small_font = pygame.font.Font(None, UI_SMALL_FONT_SIZE) + + self.init_game_world() + + self.messages = [] + self.message_duration = 3.0 + + def init_game_world(self): + self.track_network = TrackNetwork() + + self.create_level_1_tracks() + + self.switch_controller = SwitchController(self.track_network) + self.scheduler = TrainScheduler(self.track_network) + self.time_system = TimeSystem() + self.schedule_system = ScheduleSystem(self.time_system) + self.scoring_system = ScoringSystem(self.schedule_system) + + self.trains = [] + self.train_id_counter = 0 + + self.create_initial_trains() + + self.time_system.start() + + def create_level_1_tracks(self): + n0 = self.track_network.add_node(100, 200) + n1 = self.track_network.add_node(300, 200) + n2 = self.track_network.add_node(500, 200) + n3 = self.track_network.add_node(700, 200) + n4 = self.track_network.add_node(900, 200) + n5 = self.track_network.add_node(300, 400) + n6 = self.track_network.add_node(500, 400) + n7 = self.track_network.add_node(700, 400) + n8 = self.track_network.add_node(100, 600) + n9 = self.track_network.add_node(300, 600) + n10 = self.track_network.add_node(500, 600) + n11 = self.track_network.add_node(700, 600) + n12 = self.track_network.add_node(900, 600) + + self.track_network.add_segment(n0, n1) + self.track_network.add_segment(n1, n2) + self.track_network.add_segment(n2, n3) + self.track_network.add_segment(n3, n4) + self.track_network.add_segment(n5, n6) + self.track_network.add_segment(n6, n7) + self.track_network.add_segment(n8, n9) + self.track_network.add_segment(n9, n10) + self.track_network.add_segment(n10, n11) + self.track_network.add_segment(n11, n12) + self.track_network.add_segment(n1, n5) + self.track_network.add_segment(n5, n9) + self.track_network.add_segment(n2, n6) + self.track_network.add_segment(n6, n10) + self.track_network.add_segment(n3, n7) + self.track_network.add_segment(n7, n11) + + self.track_network.add_switch(n1, [n0, n2, n5]) + self.track_network.add_switch(n2, [n1, n3, n6]) + self.track_network.add_switch(n3, [n2, n4, n7]) + self.track_network.add_switch(n5, [n1, n6, n9]) + self.track_network.add_switch(n6, [n2, n5, n7, n10]) + self.track_network.add_switch(n7, [n3, n6, n11]) + self.track_network.add_switch(n9, [n5, n8, n10]) + self.track_network.add_switch(n10, [n6, n9, n11]) + self.track_network.add_switch(n11, [n7, n10, n12]) + + self.track_network.add_station(n0, '车站A') + self.track_network.add_station(n4, '车站B') + self.track_network.add_station(n8, '车站C') + self.track_network.add_station(n12, '车站D') + self.track_network.add_station(n2, '中转站E') + self.track_network.add_station(n6, '中转站F') + self.track_network.add_station(n10, '中转站G') + + def create_initial_trains(self): + train1 = Train( + self.train_id_counter, + self.track_network, + self.track_network.stations['车站A'], + COLORS['RED'] + ) + self.train_id_counter += 1 + + route1 = [ + self.track_network.stations['车站A'], + self.track_network.nodes[1].id, + self.track_network.nodes[2].id, + self.track_network.nodes[3].id, + self.track_network.stations['车站B'] + ] + train1.set_route(route1) + self.trains.append(train1) + self.scheduler.add_train(train1) + + train2 = Train( + self.train_id_counter, + self.track_network, + self.track_network.stations['车站C'], + COLORS['BLUE'] + ) + self.train_id_counter += 1 + + route2 = [ + self.track_network.stations['车站C'], + self.track_network.nodes[9].id, + self.track_network.nodes[10].id, + self.track_network.nodes[11].id, + self.track_network.stations['车站D'] + ] + train2.set_route(route2) + self.trains.append(train2) + self.scheduler.add_train(train2) + + current_time = pygame.time.get_ticks() + self.schedule_system.create_schedule(train1.id, { + '中转站E': current_time + 15000, + '车站B': current_time + 30000 + }) + + self.schedule_system.create_schedule(train2.id, { + '中转站G': current_time + 15000, + '车站D': current_time + 30000 + }) + + def add_message(self, text, color=COLORS['WHITE']): + self.messages.append({ + 'text': text, + 'color': color, + 'time_left': self.message_duration + }) + + def handle_events(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + + elif event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + mouse_pos = pygame.mouse.get_pos() + if mouse_pos[0] < SCREEN_WIDTH - UI_PANEL_WIDTH: + switch_id = self.switch_controller.handle_click(mouse_pos) + if switch_id is not None: + self.add_message(f'道岔 {switch_id} 已切换', COLORS['YELLOW']) + self.scoring_system.add_score('efficient_switch') + + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + self.is_paused = not self.is_paused + if self.is_paused: + self.time_system.pause() + else: + self.time_system.resume() + + elif event.key == pygame.K_d: + self.show_debug = not self.show_debug + + elif event.key == pygame.K_r: + self.restart_game() + + elif event.key == pygame.K_1: + self.time_system.set_time_multiplier(1.0) + self.add_message('游戏速度: 1x', COLORS['WHITE']) + + elif event.key == pygame.K_2: + self.time_system.set_time_multiplier(2.0) + self.add_message('游戏速度: 2x', COLORS['YELLOW']) + + elif event.key == pygame.K_3: + self.time_system.set_time_multiplier(3.0) + self.add_message('游戏速度: 3x', COLORS['ORANGE']) + + def update(self, dt): + if self.is_paused or self.game_over: + return + + self.switch_controller.update(dt) + + all_collisions = [] + all_near_misses = [] + + for train in self.trains: + if not train.is_stopped: + conflicts = self.scheduler.get_conflicting_trains(train) + if conflicts: + for conflict in conflicts: + if conflict['distance'] < 40: + train.decelerate(0.5) + + train.update(dt) + + for station_name in self.track_network.stations: + if train.current_station == station_name: + if station_name in train.actual_arrival_times: + points, category = self.scoring_system.calculate_arrival_score( + train, station_name + ) + if points > 0: + self.add_message( + f'列车 {train.id} 到达 {station_name},得分 +{points}', + COLORS['GREEN'] + ) + elif points < 0: + self.add_message( + f'列车 {train.id} 晚点到达 {station_name},得分 {points}', + COLORS['RED'] + ) + + scheduler_results = self.scheduler.update(dt) + + if scheduler_results['collisions']: + for collision in scheduler_results['collisions']: + self.add_message( + f'碰撞警告! 列车 {collision["train1_id"]} 和 {collision["train2_id"]}', + COLORS['RED'] + ) + self.scoring_system.add_score('collision', collision) + + if scheduler_results['near_misses']: + for near_miss in scheduler_results['near_misses']: + self.add_message( + f'接近警告! 距离 {near_miss["distance"]:.1f}', + COLORS['ORANGE'] + ) + + for train in self.trains: + if train.has_arrived and train not in [t for t in self.trains if hasattr(t, '_completed')]: + setattr(train, '_completed', True) + self.scoring_system.add_score('train_completed', {'train_id': train.id}) + self.add_message(f'列车 {train.id} 完成行程! +{200}', COLORS['GREEN']) + + for msg in self.messages[:]: + msg['time_left'] -= dt + if msg['time_left'] <= 0: + self.messages.remove(msg) + + def draw(self): + self.screen.fill(COLORS['DARK_GRAY']) + + self.track_network.draw(self.screen) + + self.switch_controller.draw(self.screen) + + for train in self.trains: + train.draw(self.screen) + + if self.show_debug: + self.scheduler.draw_debug(self.screen) + + self.draw_ui() + self.draw_messages() + self.draw_station_labels() + + def draw_ui(self): + panel_x = SCREEN_WIDTH - UI_PANEL_WIDTH + pygame.draw.rect(self.screen, COLORS['BLACK'], + (panel_x, 0, UI_PANEL_WIDTH, SCREEN_HEIGHT)) + pygame.draw.rect(self.screen, COLORS['GRAY'], + (panel_x, 0, UI_PANEL_WIDTH, SCREEN_HEIGHT), 2) + + y = UI_PADDING + + score_data = self.scoring_system.get_detailed_score() + + title_text = self.font.render('火车调度大师', True, COLORS['YELLOW']) + self.screen.blit(title_text, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + score_text = self.font.render(f'得分: {score_data["total_score"]}', True, COLORS['WHITE']) + self.screen.blit(score_text, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + rating_text = self.font.render( + f'评级: {score_data["rating"][0]} ({score_data["rating"][1]})', + True, COLORS['GREEN'] + ) + self.screen.blit(rating_text, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + game_time = self.time_system.get_game_time_seconds() + time_text = self.font.render(f'时间: {int(game_time//60):02d}:{int(game_time%60):02d}', + True, COLORS['WHITE']) + self.screen.blit(time_text, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + speed_text = self.font.render(f'速度: {self.time_system.time_multiplier}x', + True, COLORS['YELLOW']) + self.screen.blit(speed_text, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + status_text = '暂停中' if self.is_paused else '运行中' + status_color = COLORS['YELLOW'] if self.is_paused else COLORS['GREEN'] + status_render = self.font.render(f'状态: {status_text}', True, status_color) + self.screen.blit(status_render, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + 10 + + trains_title = self.font.render('列车状态:', True, COLORS['WHITE']) + self.screen.blit(trains_title, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + for train in self.trains: + train_text = f'列车 {train.id}: ' + if train.is_stopped: + train_text += '停止' + elif train.is_at_station: + train_text += f'停靠 {train.current_station}' + elif train.has_arrived: + train_text += '已完成' + else: + train_text += '运行中' + + train_color = COLORS['RED'] if train.is_stopped else COLORS['GREEN'] + train_render = self.small_font.render(train_text, True, train_color) + self.screen.blit(train_render, (panel_x + UI_PADDING, y)) + y += UI_SMALL_FONT_SIZE + UI_PADDING + + y += 10 + stats_title = self.font.render('统计:', True, COLORS['WHITE']) + self.screen.blit(stats_title, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + stats = score_data['statistics'] + stats_text = [ + f'正点: {stats["on_time_arrivals"]}', + f'延误: {stats["delayed_arrivals"]}', + f'碰撞: {stats["collisions"]}', + f'完成: {stats["trains_completed"]}' + ] + + for text in stats_text: + stats_render = self.small_font.render(text, True, COLORS['LIGHT_GRAY']) + self.screen.blit(stats_render, (panel_x + UI_PADDING, y)) + y += UI_SMALL_FONT_SIZE + UI_PADDING + + y += 10 + help_title = self.font.render('控制:', True, COLORS['WHITE']) + self.screen.blit(help_title, (panel_x + UI_PADDING, y)) + y += UI_FONT_SIZE + UI_PADDING + + help_texts = [ + '点击道岔: 切换轨道', + '空格: 暂停/继续', + '1/2/3: 速度调整', + 'R: 重新开始', + 'D: 调试视图' + ] + + for text in help_texts: + help_render = self.small_font.render(text, True, COLORS['GRAY']) + self.screen.blit(help_render, (panel_x + UI_PADDING, y)) + y += UI_SMALL_FONT_SIZE + UI_PADDING + + def draw_messages(self): + y = 10 + for msg in self.messages: + alpha = min(255, int(msg['time_left'] / self.message_duration * 255)) + color = ( + min(255, msg['color'][0] + (255 - msg['color'][0]) * (1 - alpha/255)), + min(255, msg['color'][1] + (255 - msg['color'][1]) * (1 - alpha/255)), + min(255, msg['color'][2] + (255 - msg['color'][2]) * (1 - alpha/255)) + ) + + msg_surface = self.font.render(msg['text'], True, color) + msg_rect = msg_surface.get_rect() + msg_rect.centerx = (SCREEN_WIDTH - UI_PANEL_WIDTH) // 2 + msg_rect.y = y + self.screen.blit(msg_surface, msg_rect) + y += UI_FONT_SIZE + 5 + + def draw_station_labels(self): + for station_name, node_id in self.track_network.stations.items(): + node = self.track_network.nodes[node_id] + label = self.small_font.render(station_name, True, COLORS['WHITE']) + label_rect = label.get_rect() + label_rect.centerx = node.x + label_rect.top = node.y + 20 + self.screen.blit(label, label_rect) + + def restart_game(self): + self.game_over = False + self.is_paused = False + self.messages = [] + self.trains = [] + self.train_id_counter = 0 + + self.init_game_world() + self.add_message('游戏重新开始!', COLORS['YELLOW']) + + def run(self): + self.add_message('欢迎来到火车调度大师!', COLORS['GREEN']) + self.add_message('点击黄色道岔切换轨道方向', COLORS['YELLOW']) + + while self.running: + dt = self.clock.tick(FPS) / 1000.0 + + self.handle_events() + self.update(dt) + self.draw() + + pygame.display.flip() + + pygame.quit() + sys.exit() + + +if __name__ == '__main__': + game = TrainSchedulerGame() + game.run() diff --git a/scheduler.py b/scheduler.py new file mode 100644 index 0000000..f6667eb --- /dev/null +++ b/scheduler.py @@ -0,0 +1,187 @@ +import pygame +from config import COLLISION_DISTANCE, SAFE_DISTANCE, COLORS + + +class TrainScheduler: + def __init__(self, track_network): + self.track_network = track_network + self.trains = [] + self.collision_events = [] + self.near_miss_events = [] + self.emergency_stops = [] + + def add_train(self, train): + if train not in self.trains: + self.trains.append(train) + + def remove_train(self, train): + if train in self.trains: + self.trains.remove(train) + + def check_collisions(self): + collisions = [] + + for i in range(len(self.trains)): + for j in range(i + 1, len(self.trains)): + train1 = self.trains[i] + train2 = self.trains[j] + + distance = train1.get_distance_to_train(train2) + + if distance < COLLISION_DISTANCE: + collision_event = { + 'train1_id': train1.id, + 'train2_id': train2.id, + 'position': train1.get_position(), + 'timestamp': pygame.time.get_ticks(), + 'distance': distance + } + collisions.append(collision_event) + self.collision_events.append(collision_event) + + train1.stop() + train2.stop() + + return collisions + + def check_near_misses(self): + near_misses = [] + + for i in range(len(self.trains)): + for j in range(i + 1, len(self.trains)): + train1 = self.trains[i] + train2 = self.trains[j] + + distance = train1.get_distance_to_train(train2) + + if COLLISION_DISTANCE <= distance < SAFE_DISTANCE: + near_miss_event = { + 'train1_id': train1.id, + 'train2_id': train2.id, + 'position': train1.get_position(), + 'timestamp': pygame.time.get_ticks(), + 'distance': distance + } + near_misses.append(near_miss_event) + self.near_miss_events.append(near_miss_event) + + return near_misses + + def check_same_segment_conflicts(self): + conflicts = [] + + segment_to_trains = {} + + for train in self.trains: + if train.current_segment is not None: + seg_id = train.current_segment.id + if seg_id not in segment_to_trains: + segment_to_trains[seg_id] = [] + segment_to_trains[seg_id].append(train) + + for seg_id, trains_on_segment in segment_to_trains.items(): + if len(trains_on_segment) > 1: + conflicts.append({ + 'segment_id': seg_id, + 'trains': trains_on_segment, + 'timestamp': pygame.time.get_ticks() + }) + + for i, train in enumerate(trains_on_segment[1:]): + if train not in self.emergency_stops: + train.decelerate(1.0) + self.emergency_stops.append({ + 'train_id': train.id, + 'timestamp': pygame.time.get_ticks(), + 'reason': 'same_segment_conflict' + }) + + return conflicts + + def check_switch_conflicts(self): + conflicts = [] + + for switch_id, switch_data in self.track_network.switches.items(): + node_id = switch_data['node_id'] + node = self.track_network.nodes[node_id] + + for train in self.trains: + if train.current_node_id == node_id or train.target_node_id == node_id: + distance_to_switch = train.get_distance_to_train( + type('DummyTrain', (), {'x': node.x, 'y': node.y})() + ) + + if distance_to_switch < SAFE_DISTANCE: + conflicts.append({ + 'switch_id': switch_id, + 'train_id': train.id, + 'distance': distance_to_switch, + 'timestamp': pygame.time.get_ticks() + }) + + return conflicts + + def get_conflicting_trains(self, train): + conflicts = [] + + for other_train in self.trains: + if other_train.id == train.id: + continue + + distance = train.get_distance_to_train(other_train) + + if distance < SAFE_DISTANCE: + conflicts.append({ + 'train_id': other_train.id, + 'distance': distance, + 'is_same_segment': train.is_on_same_segment(other_train) + }) + + return conflicts + + def can_train_proceed(self, train): + conflicts = self.get_conflicting_trains(train) + + for conflict in conflicts: + if conflict['distance'] < COLLISION_DISTANCE: + return False + + return True + + def update(self, dt): + collisions = self.check_collisions() + near_misses = self.check_near_misses() + same_segment_conflicts = self.check_same_segment_conflicts() + switch_conflicts = self.check_switch_conflicts() + + return { + 'collisions': collisions, + 'near_misses': near_misses, + 'same_segment_conflicts': same_segment_conflicts, + 'switch_conflicts': switch_conflicts + } + + def draw_debug(self, screen): + for collision in self.collision_events[-5:]: + x, y = collision['position'] + pygame.draw.circle(screen, COLORS['RED'], (int(x), int(y)), 20, 3) + + for near_miss in self.near_miss_events[-10:]: + x, y = near_miss['position'] + pygame.draw.circle(screen, COLORS['YELLOW'], (int(x), int(y)), 15, 2) + + for train in self.trains: + conflicts = self.get_conflicting_trains(train) + if conflicts: + x, y = train.get_position() + for conflict in conflicts: + other_train = None + for t in self.trains: + if t.id == conflict['train_id']: + other_train = t + break + + if other_train: + ox, oy = other_train.get_position() + color = COLORS['RED'] if conflict['distance'] < COLLISION_DISTANCE else COLORS['YELLOW'] + pygame.draw.line(screen, color, (int(x), int(y)), (int(ox), int(oy)), 2) diff --git a/scoring.py b/scoring.py new file mode 100644 index 0000000..0dced10 --- /dev/null +++ b/scoring.py @@ -0,0 +1,154 @@ +from config import SCORING, SCHEDULE_PUNCTUALITY + + +class ScoringSystem: + def __init__(self, schedule_system): + self.schedule_system = schedule_system + self.total_score = 0 + self.score_history = [] + self.statistics = { + 'on_time_arrivals': 0, + 'delayed_arrivals': 0, + 'late_arrivals': 0, + 'critically_late_arrivals': 0, + 'collisions': 0, + 'efficient_switches': 0, + 'trains_completed': 0 + } + + def add_score(self, score_type, details=None): + points = 0 + + if score_type == 'on_time_arrival': + points = SCORING['ON_TIME_ARRIVAL'] + self.statistics['on_time_arrivals'] += 1 + elif score_type == 'delayed_arrival': + points = SCORING['DELAYED_ARRIVAL'] + self.statistics['delayed_arrivals'] += 1 + elif score_type == 'late_arrival': + points = SCORING['LATE_ARRIVAL'] + self.statistics['late_arrivals'] += 1 + elif score_type == 'critically_late_arrival': + points = SCORING['CRITICALLY_LATE_ARRIVAL'] + self.statistics['critically_late_arrivals'] += 1 + elif score_type == 'collision': + points = SCORING['COLLISION'] + self.statistics['collisions'] += 1 + elif score_type == 'efficient_switch': + points = SCORING['EFFICIENT_SWITCH'] + self.statistics['efficient_switches'] += 1 + elif score_type == 'train_completed': + points = SCORING['TRAIN_COMPLETED'] + self.statistics['trains_completed'] += 1 + + self.total_score += points + + self.score_history.append({ + 'type': score_type, + 'points': points, + 'details': details or {}, + 'timestamp': None + }) + + return points + + def calculate_arrival_score(self, train, station_name): + delay_seconds = self.schedule_system.calculate_punctuality(train, station_name) + + if delay_seconds is None: + return 0, None + + category = self.schedule_system.get_punctuality_category(delay_seconds) + + if category == 'on_time': + score_type = 'on_time_arrival' + elif category == 'delayed': + score_type = 'delayed_arrival' + elif category == 'late': + score_type = 'late_arrival' + else: + score_type = 'critically_late_arrival' + + points = self.add_score(score_type, { + 'train_id': train.id, + 'station_name': station_name, + 'delay_seconds': delay_seconds + }) + + return points, category + + def calculate_efficiency_score(self, trains, total_game_seconds): + if total_game_seconds <= 0: + return 0 + + completed_trains = 0 + on_time_trains = 0 + + for train in trains: + if train.has_arrived: + completed_trains += 1 + + if train.id in self.schedule_system.train_schedules: + schedule = self.schedule_system.train_schedules[train.id] + all_on_time = True + + for station_name in schedule.keys(): + delay = self.schedule_system.calculate_punctuality(train, station_name) + if delay is not None and delay >= 60: + all_on_time = False + break + + if all_on_time: + on_time_trains += 1 + + efficiency_bonus = 0 + if completed_trains > 0: + efficiency = on_time_trains / completed_trains + efficiency_bonus = int(efficiency * 100) + + return efficiency_bonus + + def get_rating(self): + if self.total_score >= 1000: + return 'S', '调度大师' + elif self.total_score >= 700: + return 'A', '优秀调度员' + elif self.total_score >= 400: + return 'B', '合格调度员' + elif self.total_score >= 100: + return 'C', '见习调度员' + else: + return 'D', '需要学习' + + def get_detailed_score(self): + total_arrivals = ( + self.statistics['on_time_arrivals'] + + self.statistics['delayed_arrivals'] + + self.statistics['late_arrivals'] + + self.statistics['critically_late_arrivals'] + ) + + punctuality_rate = 0.0 + if total_arrivals > 0: + punctuality_rate = self.statistics['on_time_arrivals'] / total_arrivals + + return { + 'total_score': self.total_score, + 'statistics': self.statistics.copy(), + 'punctuality_rate': punctuality_rate, + 'total_arrivals': total_arrivals, + 'rating': self.get_rating() + } + + def reset(self): + self.total_score = 0 + self.score_history = [] + self.statistics = { + 'on_time_arrivals': 0, + 'delayed_arrivals': 0, + 'late_arrivals': 0, + 'critically_late_arrivals': 0, + 'collisions': 0, + 'efficient_switches': 0, + 'trains_completed': 0 + } diff --git a/switch_control.py b/switch_control.py new file mode 100644 index 0000000..c6c581c --- /dev/null +++ b/switch_control.py @@ -0,0 +1,88 @@ +import pygame +from config import COLORS, SWITCH_RADIUS + + +class SwitchController: + def __init__(self, track_network): + self.track_network = track_network + self.active_switch = None + self.switch_cooldown = 0 + self.switch_history = [] + + def handle_click(self, mouse_pos): + if self.switch_cooldown > 0: + return None + + x, y = mouse_pos + + for switch_id, switch_data in self.track_network.switches.items(): + node_id = switch_data['node_id'] + node = self.track_network.nodes[node_id] + + dx = node.x - x + dy = node.y - y + distance = (dx*dx + dy*dy) ** 0.5 + + if distance < SWITCH_RADIUS + 10: + if self._can_switch(switch_id): + self._toggle_switch(switch_id) + self.switch_cooldown = 0.5 + self.switch_history.append({ + 'switch_id': switch_id, + 'timestamp': pygame.time.get_ticks(), + 'new_option': switch_data['current_option'] + }) + return switch_id + + return None + + def _can_switch(self, switch_id): + switch_data = self.track_network.switches.get(switch_id) + if not switch_data: + return False + + return True + + def _toggle_switch(self, switch_id): + self.track_network.toggle_switch(switch_id) + + def get_switch_info(self, switch_id): + switch_data = self.track_network.switches.get(switch_id) + if not switch_data: + return None + + node_id = switch_data['node_id'] + node = self.track_network.nodes[node_id] + + return { + 'switch_id': switch_id, + 'node_id': node_id, + 'position': (node.x, node.y), + 'options': switch_data['options'], + 'current_option_index': switch_data['current_option'], + 'current_option': switch_data['options'][switch_data['current_option']] + } + + def update(self, dt): + if self.switch_cooldown > 0: + self.switch_cooldown -= dt + + def draw(self, screen): + for switch_id, switch_data in self.track_network.switches.items(): + node_id = switch_data['node_id'] + node = self.track_network.nodes[node_id] + x, y = node.x, node.y + + for i, option in enumerate(switch_data['options']): + is_current = (i == switch_data['current_option']) + target_node = self.track_network.nodes.get(option) + + if target_node: + line_color = COLORS['GREEN'] if is_current else COLORS['RED'] + pygame.draw.line(screen, line_color, + (int(x), int(y)), + (int(target_node.x), int(target_node.y)), + 2 if is_current else 1) + + indicator_color = COLORS['YELLOW'] if self.switch_cooldown > 0 else COLORS['WHITE'] + pygame.draw.circle(screen, indicator_color, (int(x), int(y)), SWITCH_RADIUS + 2, 2) diff --git a/time_system.py b/time_system.py new file mode 100644 index 0000000..fad1c63 --- /dev/null +++ b/time_system.py @@ -0,0 +1,147 @@ +import pygame +from config import GAME_TIME_MULTIPLIER, SCHEDULE_PUNCTUALITY + + +class TimeSystem: + def __init__(self): + self.game_start_time = 0 + self.real_start_time = 0 + self.is_paused = False + self.time_multiplier = GAME_TIME_MULTIPLIER + self.pause_time = 0 + self.total_paused_time = 0 + + def start(self): + self.real_start_time = pygame.time.get_ticks() + self.game_start_time = 0 + self.total_paused_time = 0 + + def pause(self): + if not self.is_paused: + self.is_paused = True + self.pause_time = pygame.time.get_ticks() + + def resume(self): + if self.is_paused: + self.is_paused = False + paused_duration = pygame.time.get_ticks() - self.pause_time + self.total_paused_time += paused_duration + + def get_game_time(self): + if self.is_paused: + elapsed = self.pause_time - self.real_start_time - self.total_paused_time + else: + elapsed = pygame.time.get_ticks() - self.real_start_time - self.total_paused_time + + return elapsed * self.time_multiplier + + def get_game_time_seconds(self): + return self.get_game_time() / 1000.0 + + def format_time(self, milliseconds): + seconds = milliseconds / 1000.0 + minutes = int(seconds // 60) + seconds = int(seconds % 60) + return f"{minutes:02d}:{seconds:02d}" + + def set_time_multiplier(self, multiplier): + if multiplier > 0: + self.time_multiplier = multiplier + + +class ScheduleSystem: + def __init__(self, time_system): + self.time_system = time_system + self.train_schedules = {} + self.station_schedules = {} + + def create_schedule(self, train_id, station_times): + schedule = {} + for station_name, arrival_time_ms in station_times.items(): + schedule[station_name] = arrival_time_ms + + self.train_schedules[train_id] = schedule + + for station_name in schedule.keys(): + if station_name not in self.station_schedules: + self.station_schedules[station_name] = [] + self.station_schedules[station_name].append({ + 'train_id': train_id, + 'arrival_time': schedule[station_name] + }) + + def get_train_schedule(self, train_id): + return self.train_schedules.get(train_id, None) + + def get_station_schedule(self, station_name): + return self.station_schedules.get(station_name, []) + + def calculate_punctuality(self, train, station_name): + if train.id not in self.train_schedules: + return None + + schedule = self.train_schedules[train.id] + if station_name not in schedule: + return None + + if station_name not in train.actual_arrival_times: + return None + + scheduled_arrival = schedule[station_name] + actual_arrival = train.actual_arrival_times[station_name] + + delay_ms = actual_arrival - scheduled_arrival + delay_seconds = delay_ms / 1000.0 + + return delay_seconds + + def get_punctuality_category(self, delay_seconds): + if delay_seconds is None: + return None + + if delay_seconds < 60: + return 'on_time' + elif delay_seconds < 300: + return 'delayed' + elif delay_seconds < 600: + return 'late' + else: + return 'critically_late' + + def get_punctuality_score(self, delay_seconds): + category = self.get_punctuality_category(delay_seconds) + + if category == 'on_time': + return SCHEDULE_PUNCTUALITY['ON_TIME'] + elif category == 'delayed': + return SCHEDULE_PUNCTUALITY['DELAYED'] + elif category == 'late': + return SCHEDULE_PUNCTUALITY['LATE'] + elif category == 'critically_late': + return SCHEDULE_PUNCTUALITY['CRITICALLY_LATE'] + else: + return 0 + + def get_overall_punctuality(self, trains): + total_score = 0.0 + count = 0 + + for train in trains: + if train.id not in self.train_schedules: + continue + + schedule = self.train_schedules[train.id] + for station_name in schedule.keys(): + delay = self.calculate_punctuality(train, station_name) + if delay is not None: + score = self.get_punctuality_score(delay) + total_score += score + count += 1 + + if count == 0: + return 0.0 + + return total_score / count + + def update(self, dt): + pass diff --git a/track.py b/track.py new file mode 100644 index 0000000..d2bd512 --- /dev/null +++ b/track.py @@ -0,0 +1,203 @@ +import pygame +import math +from config import COLORS, TRACK_WIDTH, SWITCH_RADIUS, STATION_RADIUS + + +class TrackNode: + def __init__(self, node_id, x, y, node_type='regular'): + self.id = node_id + self.x = x + self.y = y + self.node_type = node_type + self.connections = [] + self.is_station = False + self.station_name = None + + def add_connection(self, node_id): + if node_id not in self.connections: + self.connections.append(node_id) + + def remove_connection(self, node_id): + if node_id in self.connections: + self.connections.remove(node_id) + + def get_position(self): + return (self.x, self.y) + + def distance_to(self, other_node): + dx = self.x - other_node.x + dy = self.y - other_node.y + return math.sqrt(dx*dx + dy*dy) + + def draw(self, screen, is_active=False): + color = COLORS['WHITE'] + if self.node_type == 'switch': + color = COLORS['YELLOW'] if is_active else COLORS['ORANGE'] + pygame.draw.circle(screen, color, (int(self.x), int(self.y)), SWITCH_RADIUS) + pygame.draw.circle(screen, COLORS['BLACK'], (int(self.x), int(self.y)), SWITCH_RADIUS - 2) + elif self.is_station: + color = COLORS['GREEN'] + pygame.draw.circle(screen, color, (int(self.x), int(self.y)), STATION_RADIUS) + pygame.draw.circle(screen, COLORS['WHITE'], (int(self.x), int(self.y)), STATION_RADIUS - 3) + else: + pygame.draw.circle(screen, color, (int(self.x), int(self.y)), 3) + + +class TrackSegment: + def __init__(self, segment_id, start_node, end_node): + self.id = segment_id + self.start_node = start_node + self.end_node = end_node + self.length = start_node.distance_to(end_node) + self.is_blocked = False + + def get_length(self): + return self.length + + def get_position_at(self, progress): + if progress < 0: + progress = 0 + elif progress > 1: + progress = 1 + + x = self.start_node.x + (self.end_node.x - self.start_node.x) * progress + y = self.start_node.y + (self.end_node.y - self.start_node.y) * progress + return (x, y) + + def get_direction(self): + dx = self.end_node.x - self.start_node.x + dy = self.end_node.y - self.start_node.y + angle = math.atan2(dy, dx) + return angle + + def draw(self, screen): + pygame.draw.line(screen, COLORS['BROWN'], + (int(self.start_node.x), int(self.start_node.y)), + (int(self.end_node.x), int(self.end_node.y)), + TRACK_WIDTH) + pygame.draw.line(screen, COLORS['GRAY'], + (int(self.start_node.x), int(self.start_node.y)), + (int(self.end_node.x), int(self.end_node.y)), + TRACK_WIDTH - 4) + + +class TrackNetwork: + def __init__(self): + self.nodes = {} + self.segments = {} + self.switches = {} + self.stations = {} + self.next_node_id = 0 + self.next_segment_id = 0 + self.next_switch_id = 0 + + def add_node(self, x, y, node_type='regular'): + node_id = self.next_node_id + self.nodes[node_id] = TrackNode(node_id, x, y, node_type) + self.next_node_id += 1 + return node_id + + def add_segment(self, start_node_id, end_node_id): + if start_node_id not in self.nodes or end_node_id not in self.nodes: + return None + + start_node = self.nodes[start_node_id] + end_node = self.nodes[end_node_id] + + segment_id = self.next_segment_id + segment = TrackSegment(segment_id, start_node, end_node) + self.segments[segment_id] = segment + + start_node.add_connection(end_node_id) + end_node.add_connection(start_node_id) + + self.next_segment_id += 1 + return segment_id + + def add_switch(self, node_id, options): + if node_id not in self.nodes: + return None + + switch_id = self.next_switch_id + self.switches[switch_id] = { + 'node_id': node_id, + 'options': options, + 'current_option': 0 + } + self.nodes[node_id].node_type = 'switch' + self.next_switch_id += 1 + return switch_id + + def toggle_switch(self, switch_id): + if switch_id not in self.switches: + return False + + switch = self.switches[switch_id] + switch['current_option'] = (switch['current_option'] + 1) % len(switch['options']) + return True + + def get_switch_current_option(self, switch_id): + if switch_id not in self.switches: + return None + switch = self.switches[switch_id] + return switch['options'][switch['current_option']] + + def add_station(self, node_id, station_name): + if node_id not in self.nodes: + return False + + self.nodes[node_id].is_station = True + self.nodes[node_id].station_name = station_name + self.stations[station_name] = node_id + return True + + def get_station_node(self, station_name): + if station_name not in self.stations: + return None + return self.nodes[self.stations[station_name]] + + def get_segment_between(self, start_node_id, end_node_id): + for seg_id, segment in self.segments.items(): + if (segment.start_node.id == start_node_id and segment.end_node.id == end_node_id) or \ + (segment.start_node.id == end_node_id and segment.end_node.id == start_node_id): + return segment + return None + + def find_path(self, start_node_id, end_node_id, visited=None): + if visited is None: + visited = set() + + if start_node_id == end_node_id: + return [start_node_id] + + visited.add(start_node_id) + start_node = self.nodes[start_node_id] + + for neighbor_id in start_node.connections: + if neighbor_id not in visited: + path = self.find_path(neighbor_id, end_node_id, visited.copy()) + if path: + return [start_node_id] + path + + return None + + def draw(self, screen): + for segment in self.segments.values(): + segment.draw(screen) + + for node in self.nodes.values(): + is_active = False + for switch in self.switches.values(): + if switch['node_id'] == node.id: + is_active = True + break + node.draw(screen, is_active) + + def get_node_by_position(self, x, y, tolerance=15): + for node_id, node in self.nodes.items(): + dx = node.x - x + dy = node.y - y + distance = math.sqrt(dx*dx + dy*dy) + if distance < tolerance: + return node_id + return None diff --git a/train.py b/train.py new file mode 100644 index 0000000..b7c7210 --- /dev/null +++ b/train.py @@ -0,0 +1,188 @@ +import pygame +import math +from config import COLORS, TRAIN_COLORS, TRAIN_SPEED, TRAIN_LENGTH, TRAIN_WIDTH + + +class Train: + def __init__(self, train_id, track_network, start_node_id, color=None): + self.id = train_id + self.track_network = track_network + self.start_node_id = start_node_id + self.current_node_id = start_node_id + self.target_node_id = None + self.current_segment = None + self.progress_along_segment = 0.0 + + self.color = color if color else TRAIN_COLORS[train_id % len(TRAIN_COLORS)] + self.speed = TRAIN_SPEED['DEFAULT'] + self.max_speed = TRAIN_SPEED['MAX'] + self.min_speed = TRAIN_SPEED['MIN'] + + self.x, self.y = self.track_network.nodes[start_node_id].get_position() + self.angle = 0.0 + + self.is_stopped = False + self.is_at_station = False + self.current_station = None + self.station_wait_time = 0 + + self.route = [] + self.current_route_index = 0 + self.has_arrived = False + + self.schedule = None + self.actual_arrival_times = {} + self.actual_departure_times = {} + + def set_route(self, node_ids): + if not node_ids: + return + + self.route = node_ids.copy() + self.current_route_index = 0 + self.current_node_id = node_ids[0] + if len(node_ids) > 1: + self.target_node_id = node_ids[1] + self._update_segment() + self.x, self.y = self.track_network.nodes[node_ids[0]].get_position() + self.has_arrived = False + + def _update_segment(self): + if self.current_node_id is not None and self.target_node_id is not None: + self.current_segment = self.track_network.get_segment_between( + self.current_node_id, self.target_node_id + ) + if self.current_segment: + self.progress_along_segment = 0.0 + + def set_speed(self, speed): + self.speed = max(self.min_speed, min(speed, self.max_speed)) + + def accelerate(self, delta): + self.set_speed(self.speed + delta) + + def decelerate(self, delta): + self.set_speed(self.speed - delta) + + def stop(self): + self.is_stopped = True + + def start(self): + self.is_stopped = False + + def update(self, dt): + if self.is_stopped or self.has_arrived: + return + + if self.is_at_station: + self.station_wait_time -= dt + if self.station_wait_time <= 0: + self.is_at_station = False + self.station_wait_time = 0 + if self.current_station: + self.actual_departure_times[self.current_station] = pygame.time.get_ticks() + return + + if self.current_segment is None: + if len(self.route) > self.current_route_index + 1: + self.target_node_id = self.route[self.current_route_index + 1] + self._update_segment() + else: + self.has_arrived = True + if self.current_station: + self.actual_arrival_times[self.current_station] = pygame.time.get_ticks() + return + + segment_length = self.current_segment.get_length() + if segment_length <= 0: + return + + move_distance = self.speed * dt * 60 + progress_delta = move_distance / segment_length + + self.progress_along_segment += progress_delta + + if self.progress_along_segment >= 1.0: + self.progress_along_segment = 0.0 + self.current_node_id = self.target_node_id + self.current_route_index += 1 + + current_node = self.track_network.nodes[self.current_node_id] + if current_node.is_station: + self.is_at_station = True + self.current_station = current_node.station_name + self.station_wait_time = 3.0 + self.actual_arrival_times[current_node.station_name] = pygame.time.get_ticks() + + if self.current_route_index < len(self.route) - 1: + self.target_node_id = self.route[self.current_route_index + 1] + self._update_segment() + else: + self.has_arrived = True + self.current_segment = None + + if self.current_segment: + self.x, self.y = self.current_segment.get_position_at(self.progress_along_segment) + self.angle = self.current_segment.get_direction() + + def get_position(self): + return (self.x, self.y) + + def get_angle(self): + return self.angle + + def get_distance_to_train(self, other_train): + dx = self.x - other_train.x + dy = self.y - other_train.y + return math.sqrt(dx*dx + dy*dy) + + def is_on_same_segment(self, other_train): + if self.current_segment is None or other_train.current_segment is None: + return False + return self.current_segment.id == other_train.current_segment.id + + def get_punctuality(self, station_name): + if self.schedule is None: + return None + + if station_name not in self.schedule: + return None + + scheduled_arrival = self.schedule[station_name] + if station_name not in self.actual_arrival_times: + return None + + actual_arrival = self.actual_arrival_times[station_name] + delay_seconds = (actual_arrival - scheduled_arrival) / 1000.0 + + return delay_seconds + + def draw(self, screen): + train_length = TRAIN_LENGTH + train_width = TRAIN_WIDTH + + front_x = self.x + math.cos(self.angle) * (train_length / 2) + front_y = self.y + math.sin(self.angle) * (train_length / 2) + back_x = self.x - math.cos(self.angle) * (train_length / 2) + back_y = self.y - math.sin(self.angle) * (train_length / 2) + + perp_angle = self.angle + math.pi / 2 + dx = math.cos(perp_angle) * (train_width / 2) + dy = math.sin(perp_angle) * (train_width / 2) + + points = [ + (front_x + dx, front_y + dy), + (front_x - dx, front_y - dy), + (back_x - dx, back_y - dy), + (back_x + dx, back_y + dy) + ] + + pygame.draw.polygon(screen, self.color, points) + pygame.draw.polygon(screen, COLORS['BLACK'], points, 1) + + headlight_x = front_x + math.cos(self.angle) * 2 + headlight_y = front_y + math.sin(self.angle) * 2 + pygame.draw.circle(screen, COLORS['YELLOW'], (int(headlight_x), int(headlight_y)), 2) + + if self.is_at_station: + pygame.draw.circle(screen, COLORS['WHITE'], (int(self.x), int(self.y) - 20), 5) From a55826873b009ba7930d552a744c48bb1b5a124e Mon Sep 17 00:00:00 2001 From: Clumsy Penguin <16216763+darkenvironment@user.noreply.gitee.com> Date: Sun, 3 May 2026 09:55:24 +0800 Subject: [PATCH 2/3] ww --- main.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 5f3209c..97c899b 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import pygame import sys import math +import os from config import ( SCREEN_WIDTH, SCREEN_HEIGHT, FPS, COLORS, UI_PANEL_WIDTH, @@ -14,6 +15,47 @@ from scoring import ScoringSystem +def load_chinese_font(size): + font_paths = [ + r'C:\Windows\Fonts\msyh.ttc', + r'C:\Windows\Fonts\simsun.ttc', + r'C:\Windows\Fonts\simhei.ttf', + r'C:\Windows\Fonts\simkai.ttf', + r'C:\Windows\Fonts\STKAITI.TTF', + r'C:\Windows\Fonts\STSONG.TTF', + r'C:\Windows\Fonts\SIMYOU.TTF', + ] + + for font_path in font_paths: + if os.path.exists(font_path): + try: + font = pygame.font.Font(font_path, size) + return font + except: + continue + + try: + font = pygame.font.SysFont('microsoftyahei', size) + return font + except: + pass + + try: + font = pygame.font.SysFont('simsun', size) + return font + except: + pass + + try: + font = pygame.font.SysFont('simhei', size) + return font + except: + pass + + font = pygame.font.Font(None, size) + return font + + class TrainSchedulerGame: def __init__(self): pygame.init() @@ -28,8 +70,8 @@ def __init__(self): self.game_over = False self.show_debug = False - self.font = pygame.font.Font(None, UI_FONT_SIZE) - self.small_font = pygame.font.Font(None, UI_SMALL_FONT_SIZE) + self.font = load_chinese_font(UI_FONT_SIZE) + self.small_font = load_chinese_font(UI_SMALL_FONT_SIZE) self.init_game_world() From 70ae7ef7cc35e6ecab3131eea7ff47c99c7a81f4 Mon Sep 17 00:00:00 2001 From: Clumsy Penguin <16216763+darkenvironment@user.noreply.gitee.com> Date: Sun, 3 May 2026 10:17:51 +0800 Subject: [PATCH 3/3] ee --- main.py | 44 +++++++++++++++++++++++--------------------- train.py | 1 + 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 97c899b..ade89d6 100644 --- a/main.py +++ b/main.py @@ -128,15 +128,15 @@ def create_level_1_tracks(self): self.track_network.add_segment(n3, n7) self.track_network.add_segment(n7, n11) - self.track_network.add_switch(n1, [n0, n2, n5]) - self.track_network.add_switch(n2, [n1, n3, n6]) - self.track_network.add_switch(n3, [n2, n4, n7]) - self.track_network.add_switch(n5, [n1, n6, n9]) - self.track_network.add_switch(n6, [n2, n5, n7, n10]) - self.track_network.add_switch(n7, [n3, n6, n11]) - self.track_network.add_switch(n9, [n5, n8, n10]) - self.track_network.add_switch(n10, [n6, n9, n11]) - self.track_network.add_switch(n11, [n7, n10, n12]) + self.track_network.add_switch(n1, [n2, n5, n0]) + self.track_network.add_switch(n2, [n3, n6, n1]) + self.track_network.add_switch(n3, [n4, n7, n2]) + self.track_network.add_switch(n5, [n6, n9, n1]) + self.track_network.add_switch(n6, [n7, n10, n2, n5]) + self.track_network.add_switch(n7, [n11, n3, n6]) + self.track_network.add_switch(n9, [n10, n5, n8]) + self.track_network.add_switch(n10, [n11, n6, n9]) + self.track_network.add_switch(n11, [n12, n7, n10]) self.track_network.add_station(n0, '车站A') self.track_network.add_station(n4, '车站B') @@ -265,19 +265,21 @@ def update(self, dt): for station_name in self.track_network.stations: if train.current_station == station_name: if station_name in train.actual_arrival_times: - points, category = self.scoring_system.calculate_arrival_score( - train, station_name - ) - if points > 0: - self.add_message( - f'列车 {train.id} 到达 {station_name},得分 +{points}', - COLORS['GREEN'] - ) - elif points < 0: - self.add_message( - f'列车 {train.id} 晚点到达 {station_name},得分 {points}', - COLORS['RED'] + if station_name not in train.notified_arrivals: + points, category = self.scoring_system.calculate_arrival_score( + train, station_name ) + if points > 0: + self.add_message( + f'列车 {train.id} 到达 {station_name},得分 +{points}', + COLORS['GREEN'] + ) + elif points < 0: + self.add_message( + f'列车 {train.id} 晚点到达 {station_name},得分 {points}', + COLORS['RED'] + ) + train.notified_arrivals.add(station_name) scheduler_results = self.scheduler.update(dt) diff --git a/train.py b/train.py index b7c7210..75735a6 100644 --- a/train.py +++ b/train.py @@ -33,6 +33,7 @@ def __init__(self, train_id, track_network, start_node_id, color=None): self.schedule = None self.actual_arrival_times = {} self.actual_departure_times = {} + self.notified_arrivals = set() def set_route(self, node_ids): if not node_ids: