/* trsim.c - Created by Giampiero Caprino This file is part of Train Director Train Director is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Train Director is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Train Director; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #if !defined(__unix__) #include #endif #include #include "ask.h" #include "html.h" #include "trsim.h" char *version = "1.19c"; #if defined(__linux__) char *host = " Linux"; #elif defined(__FreeBSD__) char *host = " Freebsd"; #else char *host = ""; #endif struct _conf conf; grcolor color_white; grcolor color_black; grcolor color_green; grcolor color_yellow; grcolor color_red; grcolor color_orange; grcolor color_brown; grcolor color_gray; grcolor color_lightgray; grcolor color_darkgray; grcolor color_blue; grcolor color_cyan; void (*track_properties_dialog)(Track *); void (*signal_properties_dialog)(Track *); void (*trigger_properties_dialog)(Track *); void (*performance_dialog)(void); void (*options_dialog)(void); void (*select_day_dialog)(void); void (*train_info_dialog)(Train *t); void (*assign_dialog)(Train *t); void (*station_sched_dialog)(char *); void (*itinerary_dialog)(Itinerary *it); void (*about_dialog)(void); TrButton buttonRow[] = { { "Start", "run" }, { "Restart", "t0" }, { "Fast", "fast" }, { "Slow", "slow" }, { "Sched.", "sched" }, { 0 } }; extern int ntoolrows; extern int screen_width; int is_windows; int time_mults[] = { 1, 2, 3, 5, 7, 10, 15, 20, 30, 60, 120, 240, 300, -1 }; int cur_time_mult = 5; /* start with T x 10 */ long start_time; int show_speeds = 1; int signal_traditional = 1; int show_blocks = 1; int beep_on_alert = 1; int beep_on_enter = 0; int show_seconds; int hard_counters = 0; int platform_schedule; int show_canceled = 1; /* windows only */ int showing_graph = 0; /* windows only */ FILE *flog; FILE *frply; char entering_time[20]; char leaving_time[20]; char current_speed[20]; char current_delay[20]; char current_late[20]; char current_status[250]; char current_project[250]; /* name of files that we loaded */ char *curpath; char *disp_columns[9] = { entering_time, "", "", leaving_time, "", current_speed, current_delay, current_late, current_status }; Track *layout; Train *schedule; Train *stranded; TextList *track_info; Itinerary *itineraries; Track *signal_list, *track_list, *text_list, *switch_list; struct tr_rect cliprect; int ignore_cliprect; unsigned char update_map[(XMAX / HGRID) * (YMAX / VGRID)]; #define UPDATE_MAP(x, y) (update_map[(y) * (XMAX / HGRID) + (x)]) struct station_sched *stat_sched; perf perf_easy = { /* performance tracking */ 100, /* wrong dest */ 10, /* late trains */ 1, /* thrown switch */ 1, /* cleared signal */ 1, /* command denied */ 0, /* turned train */ 0, /* waiting train */ 5, /* wrong platform */ 0, /* number of late trains */ 0, /* number of wrong destinations */ 0, /* number of missed stops */ 0, /* wrong rolling stock assignments */ }; perf perf_hard = { /* performance tracking */ 100, /* wrong dest */ 10, /* late trains */ 1, /* thrown switch */ 3, /* cleared signal */ 1, /* command denied */ 1, /* turned train */ 1, /* waiting train */ 5, /* wrong platform */ 0, /* number of late trains */ 0, /* number of wrong destinations */ 0, /* number of missed stops */ 5 /* wrong rolling stock assignments */ }; perf perf_vals; /* currrent performance values */ perf perf_tot; /* performace counters */ int editing; int editing_itinerary; int running; int run_points; int total_delay; int total_late; int time_mult; long current_time; int run_day; int total_track_number; /* to prevent endless loops in findPath() */ int run_point_base = 1; /* 10 points per second travelled */ char time_msg[128]; char total_points_msg[128]; char delay_points_msg[64]; char time_mult_msg[32]; char late_points_msg[256]; char alert_msg[256]; char status_line[256]; char dummy_line[20]; TrLabel labelList[] = { { time_msg }, { total_points_msg }, { alert_msg }, { time_mult_msg }, { delay_points_msg }, { late_points_msg }, { dummy_line }, /* could be used for additional msgs */ { status_line }, { 0 } }; struct edittools tooltbl1024[] = { { TEXT, 0, 0, 0 }, { TRACK, W_E, 0, 1 }, { TRACK, NW_SE, 1, 0 }, { TRACK, SW_NE, 1, 1 }, { TRACK, W_NE, 2, 0 }, { TRACK, W_SE, 2, 1 }, { TRACK, NW_E, 3, 0 }, { TRACK, SW_E, 3, 1 }, { TRACK, XH_NW_SE, 23, 0 }, { TRACK, XH_SW_NE, 23, 1 }, { TRACK, X_X, 24, 0 }, { TRACK, X_PLUS, 24, 1 }, { SWITCH, 0, 4, 0 }, { SWITCH, 1, 4, 1 }, { SWITCH, 2, 5, 0 }, { SWITCH, 3, 5, 1 }, { SWITCH, 4, 6, 0 }, { SWITCH, 5, 6, 1 }, { SWITCH, 10, 7, 0 }, { SWITCH, 11, 7, 1 }, { SWITCH, 6, 8, 0 }, { SWITCH, 7, 8, 1 }, { SWITCH, 8, 9, 0 }, { SWITCH, 9, 9, 1 }, { SWITCH, 12, 10, 0 }, /* vertical switches */ { SWITCH, 13, 10, 1 }, { SWITCH, 14, 11, 0 }, { SWITCH, 15, 11, 1 }, { TRACK, NW_S, 12, 0 }, { TRACK, SW_N, 12, 1 }, { TRACK, NE_S, 13, 0 }, { TRACK, SE_N, 13, 1 }, { TRACK, TRK_N_S, 14, 0 }, { ITIN, 0, 14, 1 }, { IMAGE, 1, 15, 0 }, { PLATFORM, 1, 15, 1 }, { TSIGNAL, 0, 16, 0 }, { TSIGNAL, 1, 16, 1 }, { TSIGNAL, 2, 17, 0 }, { TSIGNAL, 3, 17, 1 }, { TSIGNAL, S_N, 18, 0 }, { TSIGNAL, N_S, 18, 1 }, { TSIGNAL, signal_NORTH_FLEETED, 19, 0 }, { TSIGNAL, signal_SOUTH_FLEETED, 19, 1 }, { TEXT, 0, 20, 0 }, { TEXT, 1, 20, 1 }, { LINK, 0, 21, 0 }, { LINK, 1, 21, 1 }, { MACRO, 0, 22, 0 }, { MACRO, 1, 22, 1 }, { TRIGGER, W_E, 25, 0 }, { TRIGGER, E_W, 25, 1 }, { TRIGGER, N_S, 26, 0 }, { TRIGGER, S_N, 26, 1 }, { -1 } }; struct edittools tooltbl800[] = { /* used when screen is 800x600 */ { TEXT, 0, 0, 0 }, { TRACK, W_E, 0, 1 }, { TRACK, NW_SE, 1, 0 }, { TRACK, SW_NE, 1, 1 }, { TRACK, W_NE, 2, 0 }, { TRACK, W_SE, 2, 1 }, { TRACK, NW_E, 3, 0 }, { TRACK, SW_E, 3, 1 }, { TRACK, XH_NW_SE, 15, 0 }, { TRACK, XH_SW_NE, 15, 1 }, { TRACK, X_X, 16, 0 }, { TRACK, X_PLUS, 16, 1 }, { SWITCH, 0, 4, 0 }, { SWITCH, 1, 4, 1 }, { SWITCH, 2, 5, 0 }, { SWITCH, 3, 5, 1 }, { SWITCH, 4, 6, 0 }, { SWITCH, 5, 6, 1 }, { SWITCH, 10, 7, 0 }, { SWITCH, 11, 7, 1 }, { SWITCH, 6, 8, 0 }, { SWITCH, 7, 8, 1 }, { SWITCH, 8, 9, 0 }, { SWITCH, 9, 9, 1 }, { SWITCH, 12, 10, 0 }, /* vertical switches */ { SWITCH, 13, 10, 1 }, { SWITCH, 14, 11, 0 }, { SWITCH, 15, 11, 1 }, { TRACK, NW_S, 12, 0 }, { TRACK, SW_N, 12, 1 }, { TRACK, NE_S, 13, 0 }, { TRACK, SE_N, 13, 1 }, { TRACK, TRK_N_S, 14, 0 }, { ITIN, 0, 14, 1 }, { IMAGE, 1, 15, 2 }, { PLATFORM, 1, 0, 2 }, { TSIGNAL, 0, 1, 2 }, { TSIGNAL, 1, 2, 2 }, { TSIGNAL, 2, 3, 2 }, { TSIGNAL, 3, 4, 2 }, { TSIGNAL, S_N, 5, 2 }, { TSIGNAL, N_S, 6, 2 }, { TSIGNAL, signal_NORTH_FLEETED, 7, 2 }, { TSIGNAL, signal_SOUTH_FLEETED, 8, 2 }, { TEXT, 0, 9, 2 }, { TEXT, 1, 10, 2 }, { LINK, 0, 11, 2 }, { LINK, 1, 12, 2 }, { MACRO, 0, 13, 2 }, { MACRO, 1, 14, 2 }, { TRIGGER, W_E, 17, 0 }, { TRIGGER, E_W, 17, 1 }, { TRIGGER, N_S, 18, 0 }, { TRIGGER, S_N, 18, 1 }, { -1 } }; struct edittools tooltbltracks[] = { /* used when screen is 800x600 */ { TEXT, 0, 0, 0 }, { TRACK, TRK_N_S, 0, 1 }, { TRACK, W_E, 1, 1 }, { TRACK, NW_SE, 2, 1 }, { TRACK, SW_NE, 3, 1 }, { TRACK, W_NE, 4, 1 }, { TRACK, W_SE, 5, 1 }, { TRACK, NW_E, 6, 1 }, { TRACK, SW_E, 7, 1 }, { TRACK, NW_S, 8, 1 }, { TRACK, SW_N, 9, 1 }, { TRACK, NE_S, 10, 1 }, { TRACK, SE_N, 11, 1 }, { TRACK, XH_NW_SE, 12, 1 }, { TRACK, XH_SW_NE, 13, 1 }, { TRACK, X_X, 14, 1 }, { TRACK, X_PLUS, 15, 1 }, { -1 } }; struct edittools tooltblswitches[] = { { TEXT, 0, 0, 0 }, { SWITCH, 0, 0, 1 }, { SWITCH, 1, 1, 1 }, { SWITCH, 2, 2, 1 }, { SWITCH, 3, 3, 1 }, { SWITCH, 4, 4, 1 }, { SWITCH, 5, 5, 1 }, { SWITCH, 6, 6, 1 }, { SWITCH, 7, 7, 1 }, { SWITCH, 8, 8, 1 }, { SWITCH, 9, 9, 1 }, { SWITCH, 10, 10, 1 }, { SWITCH, 11, 11, 1 }, { SWITCH, 12, 12, 1 }, /* vertical switches */ { SWITCH, 13, 13, 1 }, { SWITCH, 14, 14, 1 }, { SWITCH, 15, 15, 1 }, { SWITCH, 16, 16, 1 }, { SWITCH, 17, 17, 1 }, { SWITCH, 18, 18, 1 }, { SWITCH, 19, 19, 1 }, { SWITCH, 20, 20, 1 }, { SWITCH, 21, 21, 1 }, { SWITCH, 22, 22, 1 }, { SWITCH, 23, 23, 1 }, { -1 } }; struct edittools tooltblsignals[] = { { TEXT, 0, 0, 0 }, { TSIGNAL, 0, 0, 1 }, { TSIGNAL, 1, 1, 1 }, { TSIGNAL, 2, 2, 1 }, { TSIGNAL, 3, 3, 1 }, { TSIGNAL, S_N, 4, 1 }, { TSIGNAL, N_S, 5, 1 }, { TSIGNAL, signal_NORTH_FLEETED, 6, 1 }, { TSIGNAL, signal_SOUTH_FLEETED, 7, 1 }, { -1 } }; struct edittools tooltblmisc[] = { { TEXT, 0, 0, 0 }, { TEXT, 0, 0, 1 }, { TEXT, 1, 1, 1 }, { ITIN, 0, 2, 1 }, { ITIN, 1, 3, 1 }, { IMAGE, 1, 4, 1 }, { PLATFORM, 1, 5, 1 }, { -1 } }; struct edittools tooltblactions[] = { { TEXT, 0, 0, 0 }, { LINK, 0, 0, 1 }, { LINK, 1, 1, 1 }, { MACRO, 0, 2, 1 }, { MACRO, 1, 3, 1 }, { TRIGGER, W_E, 4, 1 }, { TRIGGER, E_W, 5, 1 }, { TRIGGER, N_S, 6, 1 }, { TRIGGER, S_N, 7, 1 }, { -1 } }; struct edittools *tooltbl = tooltbltracks /* tooltbl800 */; Track *tool_layout; Track *tool_tracks, *tool_signals, *tool_switches, *tool_misc, *tool_actions; char *en_station_titles[] = { "Train", "Arrival", "From", "Departure", "To   ", "Runs on  ", "Platform", "Notes", NULL }; char *station_titles[9]; char *station_schedule_cur; int ntrains_arrived; int ntrains_running; int ntrains_waiting; int ntrains_stopped; int ntrains_ready; void itinerary_cmd(); void remove_ext(char *buff) { char *p; /* remove extension. Will be added back by open cmd */ for(p = buff + strlen(buff); *p != ' ' && *p != '/' && *p != '\\' && *p != '.'; --p); if(*p == '.') *p = 0; } char *format_time(long tim) { static char buff[64]; sprintf(buff, "%3d:%02d ", (tim / 3600) % 24, (tim / 60) % 60); return(buff); } int parse_time(char **pp) { char *p = *pp; int v = 0, v1 = 0; while(*p == ' ') ++p; if(*p) v = *p++ - '0'; if(*p != ':') v = v * 10 + (*p++ - '0'); if(*p == ':') ++p; if(*p) v1 = *p++ - '0'; if(*p >= '0' && *p <= '9') v1 = v1 * 10 + (*p++ - '0'); *pp = p; return v * 3600 + v1 * 60; } char *parse_km(Track *t, char *p) { t->km = strtol(p, &p, 10) * 1000; if(*p == '.') t->km += strtol(++p, &p, 10) % 1000; return(p); } void compute_train_numbers(void) { Train *t; ntrains_arrived = 0; ntrains_waiting = 0; ntrains_stopped = 0; ntrains_ready = 0; ntrains_running = 0; for(t = schedule; t; t = t->next) { switch(t->status) { case train_READY: ++ntrains_ready; break; case train_RUNNING: ++ntrains_running; break; case train_WAITING: ++ntrains_waiting; break; case train_STOPPED: ++ntrains_stopped; break; case train_ARRIVED: ++ntrains_arrived; } } } long performance(void) { long tot; tot = perf_tot.wrong_dest * perf_vals.wrong_dest; tot += perf_tot.late_trains * perf_vals.late_trains; tot += perf_tot.thrown_switch * perf_vals.thrown_switch; tot += perf_tot.cleared_signal * perf_vals.cleared_signal; tot += perf_tot.turned_train * perf_vals.turned_train; tot += perf_tot.waiting_train * perf_vals.waiting_train; tot += perf_tot.wrong_platform * perf_vals.wrong_platform; tot += perf_tot.denied * perf_vals.denied; return tot; } void update_labels(void) { strcpy(time_msg, " "); if(show_seconds) sprintf(time_msg + 3, "%3ld:%02ld.%02ld ", (current_time / 3600) % 24, (current_time / 60) % 60, current_time % 60); else strcpy(time_msg + 3, format_time(current_time)); sprintf(time_msg + strlen(time_msg), " x%d ", time_mult); sprintf(time_msg + strlen(time_msg), "R %d/r %d/w %d/s %d/a %d", ntrains_running, ntrains_ready, ntrains_waiting, ntrains_stopped, ntrains_arrived); /* sprintf(time_mult_msg, " Time multiplier: %4ld", time_mult); sprintf(total_points_msg, "Performance : -%4ld", performance()); sprintf(delay_points_msg, "Delay minutes : %4ld", total_delay / 60); sprintf(late_points_msg, "Late arrivals : %4ld min", total_late); */ sprintf(total_points_msg, "Pt:%4ld, Del:%4ld, Late:%4ld", -performance(), total_delay / 60, total_late); repaint_labels(); } void print_train_info(Train *t) { strcpy(entering_time, format_time(t->timein)); strcpy(leaving_time, format_time(t->timeout)); sprintf(current_speed, "%d", t->curspeed); sprintf(current_delay, "%d", t->timedelay / 60); sprintf(current_late, "%d", t->timelate); disp_columns[4] = t->name; disp_columns[1] = t->entrance; disp_columns[2] = t->exit; strcpy(current_status, train_status(t)); /* sprintf(current_status + strlen(current_status), " pos: %ld - %ld", t->pathtravelled, t->trackpos);*/ } void invalidate_field(void) /* next time, repaint whole field */ { cliprect.top = 0; cliprect.left = 0; cliprect.bottom = 1000; cliprect.right = 2000; ignore_cliprect = 1; } void reset_clip_rect(void) /* next time, don't paint anything */ { cliprect.top = 1000; cliprect.bottom = 0; cliprect.left = 2000; cliprect.right = 0; ignore_cliprect = 0; memset(update_map, 0, sizeof(update_map)); } void change_coord(int x, int y)/* next time, paint within clip rectangle */ { if(x < cliprect.left) cliprect.left = x; if(x + 3 > cliprect.right) cliprect.right = x + 3; if(y < cliprect.top) cliprect.top = y; if(y + 1 > cliprect.bottom) cliprect.bottom = y + 1; UPDATE_MAP(x, y) = 1; UPDATE_MAP(x + 1, y) = 1; UPDATE_MAP(x + 2, y) = 1; UPDATE_MAP(x + 3, y) = 1; ++y; UPDATE_MAP(x, y) = 1; UPDATE_MAP(x + 1, y) = 1; UPDATE_MAP(x + 2, y) = 1; UPDATE_MAP(x + 3, y) = 1; } Track *init_tool_from_array(struct edittools *tbl) { int i; Track *t; Track *lst; lst = NULL; for(i = 0; tbl[i].type != -1; ++i) { t = track_new(); tbl[i].trk = t; t->x = tbl[i].x; t->y = tbl[i].y; t->type = tbl[i].type; t->direction = tbl[i].direction; t->norect = 1; t->next = lst; if(t->type == TEXT) t->station = strdup(i == 0 ? "Del" : "Abc"); else if(t->type == ITIN) t->station = strdup(" A"); else if(t->type == TSIGNAL && (t->direction & 2)) { t->fleeted = 1; t->direction &= ~2; } lst = t; } return lst; } void init_tool_layout(void) { tool_layout = init_tool_from_array(tooltbl); /* old way */ tool_tracks = init_tool_from_array(tooltbltracks);/* new way */ tool_switches = init_tool_from_array(tooltblswitches); tool_signals = init_tool_from_array(tooltblsignals); tool_misc = init_tool_from_array(tooltblmisc); tool_actions = init_tool_from_array(tooltblactions); } int track_updated(Track *t) { int i; int j; if(t->x < (cliprect.left - 1) || t->x > cliprect.right) return 0; if(t->y < (cliprect.top - 1) || t->y > cliprect.bottom) return 0; /* it's inside the clip rect, but do we really need to update it? */ #if 0 for(j = 0; j < 2; ++j) for(i = 0; i < 3; ++i) if(UPDATE_MAP(t->x, t->y)) return 1; #endif if(ignore_cliprect || UPDATE_MAP(t->x, t->y)) return 1; return 0; } void layout_paint(Track *lst) { Track *t; int x, y; if(!ignore_cliprect) for(y = cliprect.top; y <= cliprect.bottom; ++y) for(x = cliprect.left; x <= cliprect.right; ++x) if(UPDATE_MAP(x, y)) tr_fillrect(x, y); for(t = lst; t; t = t->next) if(editing || track_updated(t)) { UPDATE_MAP(t->x, t->y) = 0; track_paint(t); } } void trains_paint(Train *t) { for(; t; t = t->next) { if(t->position) { if(t->flags & TFLG_STRANDED) { if(findTrain(t->position->x, t->position->y)) continue; car_draw(t->position, t); } else train_draw(t->position, t); } if(t->tail && t->tail->position && t->tail->position != t->position) car_draw(t->tail->position, t); } } void link_all_tracks(void) { Track *t, *l; l = 0; for(t = layout; t; t = t->next) if(t->type == TRACK) { t->next1 = l; l = t; } track_list = l; l = 0; for(t = layout; t; t = t->next) if(t->type == TSIGNAL) { t->next1 = l; l = t; } signal_list = l; l = 0; for(t = layout; t; t = t->next) if(t->type == SWITCH) { t->next1 = l; l = t; } switch_list = l; l = 0; for(t = layout; t; t = t->next) if(t->type == TEXT) { t->next1 = l; l = t; } text_list = l; } void init_sim(void) { if(!tool_layout) init_tool_layout(); time_mult = 10; cur_time_mult = 5; run_points = 0; total_delay = 0; total_late = 0; alert_msg[0] = 0; } void trainsim_init(void) { Track *t; /* if(screen_width < 1000) { ntoolrows = 3; tooltbl = tooltbl800; } else */ { ntoolrows = 2; tooltbl = tooltbltracks /* tooltbl1024 */; } if(!tool_layout) init_tool_layout(); conf.fgcolor = fieldcolors[COL_TRACK]; conf.linkcolor = color_red; conf.linkcolor2 = color_blue; current_time = start_time; run_points = 0; total_delay = 0; total_late = 0; time_mult = 10; cur_time_mult = 5; alert_msg[0] = 0; perf_vals = hard_counters ? perf_hard : perf_easy; memset(&perf_tot, 0, sizeof(perf_tot)); link_all_tracks(); total_track_number = 0; for(t = track_list; t; t = t->next1) ++total_track_number; showing_graph = 0; reset_schedule(); fill_schedule(schedule, 0); compute_train_numbers(); update_labels(); } void init_all(void) { while(layout) track_delete(layout); clean_trains(schedule); schedule = 0; start_time = 0; trainsim_init(); invalidate_field(); repaint_all(); } static Track *find_in_list(Track *t, int x, int y) { for(; t; t = t->next1) if(t->x == x && t->y == y) return t; return 0; } Track *findTrackType(int x, int y, trktype type) { Track *t; switch(type) { case TRACK: return find_in_list(track_list, x, y); case TSIGNAL: return find_in_list(signal_list, x, y); case SWITCH: return find_in_list(switch_list, x, y); case TEXT: return find_in_list(text_list, x, y); } for(t = layout; t; t = t->next) if(t->x == x && t->y == y && t->type == type) return t; return 0; } Track *findLinkTo(int x, int y) { Track *t; for(t = layout; t; t = t->next) if(t->type == TEXT) { if(t->wlinkx == x && t->wlinky == y) return t; if(t->elinkx == x && t->elinky == y) return t; } return 0; } Track *findTriggerTo(int x, int y) { Track *t; for(t = layout; t; t = t->next) if(t->type == TRIGGER) { if(t->wlinkx == x && t->wlinky == y) return t; if(t->elinkx == x && t->elinky == y) return t; } return 0; } Track *findStationNamed(char *name) { Track *t; char *p; int l; l = strlen(name); if((p = strchr(name, '@'))) l = p - name; for(t = layout; t; t = t->next) { if(t->type == TRACK && t->isstation && !strncmp(name, t->station, l)) { if(!t->station[l] || t->station[l] == '@') return t; } if(t->type == TEXT && !strcmp(name, t->station) && ((t->wlinkx && t->wlinky) || (t->elinkx && t->elinky))) return t; } return 0; } Track *findStation(char *name) { Track *t, *l; for(t = layout; t; t = t->next) { if(t->type == TRACK && t->isstation) if(!strcmp(name, t->station)) return t; if(t->type == TEXT && !strcmp(name, t->station) && ((t->wlinkx && t->wlinky) || (t->elinkx && t->elinky))) return t; l = t; } return 0; } Track *findSignalNamed(char *name) { Track *t; for(t = layout; t; t = t->next) if(t->type == TSIGNAL && t->station && !strcmp(name, t->station)) return t; return 0; } Track *findItineraryNamed(char *name) { Track *t; for(t = layout; t; t = t->next) if(t->type == ITIN && t->station && !strcmp(name, t->station)) return t; return 0; } Train *findTrain(int x, int y) { Train *tr; for(tr = schedule; tr; tr = tr->next) if(tr->position && tr->position->x == x && tr->position->y == y) return tr; return 0; } Train *findTrainNamed(char *name) { Train *t; for(t = schedule; t; t = t->next) if(!strcmp(name, t->name)) return t; return 0; } Train *findStranded(int x, int y) { Train *tr; for(tr = stranded; tr; tr = tr->next) if(tr->position && tr->position->x == x && tr->position->y == y) return tr; return 0; } int sameStationPlatform(char *s1, char *s2) { if(platform_schedule) return !strcmp(s1, s2); return sameStation(s1, s2); } static Track **array_append(int *nstations, int *maxstations, Track **stations, Track *t) { int i; for(i = 0; i < *nstations; ++i) if(!platform_schedule) { if(sameStation(stations[i]->station, t->station)) break; } else if(!strcmp(stations[i]->station, t->station)) break; if(i < *nstations) /* already in list */ return stations; if(*nstations + 1 >= *maxstations) { *maxstations += 10; if(!stations) stations = (Track **)malloc(sizeof(Track *) * *maxstations); else stations = (Track **)realloc(stations, sizeof(Track *) * *maxstations); } stations[*nstations] = t; ++*nstations; return stations; } int cmp_names(char **a, char **b) { return(strcmp(a[0], b[0])); } int cmp_stations(Track **a, Track **b) { return(strcmp(a[0]->station, b[0]->station)); } Track **get_station_list(void) { Track *t; Track **stations; int nstations, maxstations; stations = 0; nstations = 0; maxstations = 0; for(t = layout; t; t = t->next) { if(!t->isstation || !t->station) continue; stations = array_append(&nstations, &maxstations, stations, t); } if(stations) { qsort(stations, nstations, sizeof(Track *), cmp_stations); stations[nstations] = 0; } return stations; } Track **get_entry_list(void) { Track *t; Track **stations; int nstations, maxstations; stations = 0; nstations = 0; maxstations = 0; for(t = layout; t; t = t->next) { if(t->type != TEXT) continue; if(t->wlinkx && t->wlinky) { } else if(t->elinkx && t->elinky) { } else continue; stations = array_append(&nstations, &maxstations, stations, t); } if(stations) { qsort(stations, nstations, sizeof(Track *), cmp_stations); stations[nstations] = 0; } return stations; } char **name_append(int *nnames, int *maxnames, char **names, char *str) { int i; for(i = 0; i < *nnames; ++i) if(sameStation(names[i], str)) break; if(i != *nnames) /* already in list */ return names; if(*nnames + 1 >= *maxnames) { *maxnames += 20; if(!names) names = (char **)malloc(sizeof(char *) * *maxnames); else names = (char **)realloc(names, sizeof(char *) * *maxnames); } names[*nnames] = str; ++*nnames; return names; } char **get_all_station_list(void) { Track *t; Train *tr; TrainStop *ts; char **names; int nnames, maxnames; names = 0; nnames = 0; maxnames = 0; for(t = layout; t; t = t->next) { if(!t->isstation || !t->station) continue; names = name_append(&nnames, &maxnames, names, t->station); } for(tr = schedule; tr; tr = tr->next) for(ts = tr->stops; ts; ts = ts->next) names = name_append(&nnames, &maxnames, names, ts->station); if(names) { qsort(names, nnames, sizeof(char *), cmp_names); names[nnames] = 0; } return names; } void do_alert(char *msg) { strcpy(alert_msg, msg); repaint_labels(); if(beep_on_alert) alert_beep(); } void pointer_at(int x, int y) { Track *t; Train *tr; if((tr = findTrain(x, y))) { sprintf(status_line, "%d,%d: %s %s", x, y, tr->name, train_status0(tr, 1)); } else if((t = findTrack(x, y)) || (t = findSwitch(x, y))) { sprintf(status_line, "%d,%d: %s %d/%d/%d/%d Km/h, %s %d m", x, y, L("speed"), t->speed[0], t->speed[1], t->speed[2], t->speed[3], L("length"), t->length); if(t->isstation) sprintf(status_line + strlen(status_line), " %s: %s", L("Station"), t->station); } else if((t = findText(x, y))) { sprintf(status_line, "%d,%d: %s %s", x, y, L("entry/exit"), t->station); } else if((t = findSignal(x, y))) { if(t->controls) sprintf(status_line, "%d,%d: %s %s %s %d, %d", x, y, L("Signal"), t->station ? t->station : "", L("controls"), t->controls->x, t->controls->y); else sprintf(status_line, "%d,%d: %s %s", x, y, L("Signal"), t->station ? t->station : ""); } else { status_line[0] = 0; } repaint_labels(); } void update_schedule(Train *t) { int i; Train *t0; for(i = 0, t0 = schedule; t0 && t0 != t; t0 = t0->next) { if(!t->entrance) continue; ++i; } if(!t0) return; print_train_info(t); gr_update_schedule(t, i); } void edit_cmd() { if(editing) return; if(editing_itinerary) /* exit edit itinerary mode */ itinerary_cmd(); editing = 1; /* enter edit layout mode */ hide_table(); show_tooltable(); repaint_all(); } void noedit_cmd() { Track *t; if(!editing) return; editing = 0; link_all_tracks(); total_track_number = 0; for(t = track_list; t; t = t->next1) ++total_track_number; hide_tooltable(); show_table(); link_signals(layout); invalidate_field(); repaint_all(); check_layout_errors(); } void itinerary_cmd() { if(editing_itinerary) { /* back to simulation mode */ editing_itinerary = 0; hide_itinerary(); show_table(); } else { if(editing) /* exit edit layout mode */ noedit_cmd(); editing_itinerary = 1; /* enter edit itinerary mode */ hide_table(); show_itinerary(); } repaint_all(); } void do_replay(void) { long issue_time; long pos; char *p; char buff[256]; while(frply) { pos = ftell(frply); if(!fgets(buff, sizeof(buff), frply)) break; buff[sizeof(buff) - 1] = 0; p = buff + strlen(buff); if(p > buff && p[-1] == '\n') --p; if(p > buff && p[-1] == '\r') --p; *p = 0; issue_time = strtoul(buff, &p, 10); if(*p == ',') ++p; if(issue_time > current_time) { /* goes into next time slice */ fseek(frply, pos, 0); /* back off to cmd start */ return; /* nothing else to do */ } trainsim_cmd(p); } if(frply) fclose(frply); } void free_station_schedule(void) { struct station_sched *sc; while((sc = stat_sched)) { stat_sched = sc->next; free(sc); } } static int stschcmp(struct station_sched **a, struct station_sched **b) { long t1, t2; if((t1 = a[0]->arrival) == -1) t1 = a[0]->departure; if((t2 = b[0]->arrival) == -1) t2 = b[0]->departure; return(t1 < t2 ? -1 : t1 == t2 ? 0 : 1); } struct station_sched *sort_station_schedule(struct station_sched *sched) { struct station_sched **qb; struct station_sched *t; int ntrains; int l; for(t = sched, ntrains = 0; t; t = t->next) ++ntrains; qb = (struct station_sched **)malloc(sizeof(struct station_sched *) * ntrains); for(t = sched, l = 0; l < ntrains; ++l, t = t->next) qb[l] = t; qsort(qb, ntrains, sizeof(struct station_sched *), stschcmp); for(l = 0; l < ntrains - 1; ++l) qb[l]->next = qb[l + 1]; qb[ntrains - 1]->next = 0; t = qb[0]; free(qb); return t; } void build_station_schedule(char *station) { Train *tr; TrainStop *ts; struct station_sched *sc; int (*cmp)(char *, char *); free_station_schedule(); for(tr = schedule; tr; tr = tr->next) { if(sameStationPlatform(tr->entrance, station)) { sc = (struct station_sched *)malloc(sizeof(struct station_sched)); memset(sc, 0, sizeof(struct station_sched)); sc->tr = tr; sc->arrival = -1; sc->departure = tr->timein; sc->stopname = tr->entrance; sc->next = stat_sched; stat_sched = sc; } else if(sameStationPlatform(tr->exit, station)) { sc = (struct station_sched *)malloc(sizeof(struct station_sched)); memset(sc, 0, sizeof(struct station_sched)); sc->tr = tr; sc->arrival = tr->timeout; sc->departure = -1; sc->stopname = tr->exit; sc->next = stat_sched; stat_sched = sc; } else for(ts = tr->stops; ts; ts = ts->next) { if(sameStationPlatform(ts->station, station)) { sc = (struct station_sched *)malloc(sizeof(struct station_sched)); memset(sc, 0, sizeof(struct station_sched)); sc->tr = tr; sc->arrival = ts->arrival; sc->departure = ts->departure; sc->stopname = ts->station; if(!ts->minstop) sc->transit = 1; sc->next = stat_sched; stat_sched = sc; break; } } } if(stat_sched) stat_sched = sort_station_schedule(stat_sched); } void do_station_list_print(void) { char *p; int i; FILE *fp; static char buff[256]; struct station_sched *sc; char buffs[8][250]; char *cols[9]; if(!station_schedule_cur) return; strcpy(buff, "station.htm"); cols[0] = buffs[0]; cols[1] = buffs[1]; cols[2] = buffs[2]; cols[3] = buffs[3]; cols[4] = buffs[4]; cols[5] = buffs[5]; cols[6] = buffs[6]; cols[7] = buffs[7]; cols[8] = NULL; if(openFileDialog(buff)) { remove_ext(buff); strcat(buff, ".htm"); if(!(fp = fopen(buff, "w"))) return; strcpy(buffs[0], station_schedule_cur); if((p = strchr(buffs[0], '@'))) *p = 0; sprintf(buff, "%s %s", L("Station of"), buffs[0]); if(p && platform_schedule) sprintf(buff + strlen(buff), " - %s %s", L("Platform"), p + 1); html_startpage(fp, buff); for(i = 0; en_station_titles[i]; ++i) station_titles[i] = L(en_station_titles[i]); html_table(station_titles); for(sc = stat_sched; sc; sc = sc->next) { /* when reassigning train stock, we consider only trains that are scheduled to depart at the same station where the assignee has arrived. */ strcpy(cols[0], sc->tr->name); if(sc->transit) { *cols[1] = 0; strcpy(cols[2], sc->tr->entrance); if((p = strchr(cols[2], '@'))) *p = 0; } else if(sc->arrival != -1) { strcpy(cols[1], format_time(sc->arrival)); strcpy(cols[2], sc->tr->entrance); if((p = strchr(cols[2], '@'))) *p = 0; } else *cols[1] = *cols[2] = 0; if(sc->departure != -1) { if(sc->transit) sprintf(cols[3], "(%s)", format_time(sc->departure)); else strcpy(cols[3], format_time(sc->departure)); strcpy(cols[4], sc->tr->exit); if((p = strchr(cols[4], '@'))) *p = 0; } else *cols[3] = *cols[4] = 0; *cols[5] = 0; for(i = 0; i < 7; ++i) if(sc->tr->days & (1 << i)) sprintf(cols[5] + strlen(cols[5]), "%d", i+1); *cols[6] = 0; if(sc->stopname && (p = strchr(sc->stopname, '@'))) sprintf(cols[6], "%s", p + 1); *cols[7] = 0; if(sc->tr->nnotes) strncpy(buffs[7], sc->tr->notes[0], sizeof(buffs[6])); html_row(cols); } html_endtable(); html_endpage(); fclose(fp); } } int all_trains_everyday(Train *t) { while(t) { if(t->days) return 0; t = t->next; } return 1; } void do_itinerary_dialog(int x, int y) { Itinerary *it = 0; Track *sig; char buff[256]; sig = findSignal(x, y); if(!sig) return; if(!sig->station || !*sig->station) { sprintf(buff, "(%d,%d)", sig->x, sig->y); sig->station = strdup(buff); } /* for(it = itineraries; it; it = it->next) if(!strcmp(it->signame, sig->station)) break; */ if(!it) { it = (Itinerary *)calloc(sizeof(Itinerary), 1); it->signame = strdup(sig->station); it->name = strdup(""); it->next = itineraries; itineraries = it; } fill_itinerary(it, sig); itinerary_dialog(it); } int set_itin_name(Itinerary *it, char *name, char *nextit) { Itinerary *it1, *it2; char *p; if((p = strchr(name, ','))) /* no commas allowed */ *p = 0; it2 = 0; for(it1 = itineraries; it1; it2 = it1, it1 = it1->next) if(it1 != it && !strcmp(name, it1->name)) { if(ask(L("An itinerary by the same name already exists.\n" "Do you want to replace the old itinerary with the new one?")) == ANSWER_YES) { if(!it2) itineraries = it1->next; else it2->next = it1->next; if(it1->signame) free(it1->signame); free(it1->name); free(it1); break; } return 0; /* let user change name */ } if(it->name) free(it->name); it->name = strdup(name); if((p = strchr(nextit, ','))) /* no commas allowed */ *p = 0; if(it->nextitin) free(it->nextitin); it->nextitin = strdup(nextit); return 1; } int set_track_properties(Track *t, char *len, char *station, char *speed, char *distance, char *wlink, char *elink) { char *p; int i; int flag; t->length = atol(len); t->speed[0] = strtol(speed, &p, 10); if(*p == '/') { t->speed[1] = strtol(++p, &p, 10); if(*p == '/') { t->speed[2] = strtol(++p, &p, 10); if(*p == '/') t->speed[3] = strtol(++p, &p, 10); } } t->isstation = 0; if(t->station) free(t->station); t->station = 0; flag = 0; if(station && *station) { t->station = strdup(station); if(t->type != TEXT && t->type != TSIGNAL) t->isstation = 1; else flag = 1; } if(*distance) parse_km(t, distance); else t->km = 0; t->wlinkx = strtol(wlink, &p, 10); if(*p == ',') ++p; t->wlinky = strtol(p, &p, 10); if(t->type == IMAGE) t->pixels = 0; /* will reaload image from file */ else if(t->type != TSIGNAL && t->type != SWITCH) { t->elinkx = strtol(elink, &p, 10); if(*p == ',') ++p; t->elinky = strtol(p, &p, 10); } return flag; } void delete_itinerary(Itinerary *ip) { Itinerary *it, *oit; oit = 0; for(it = itineraries; it && ip != it; it = it->next) oit = it; if(!it) return; if(!oit) itineraries = it->next; else oit->next = it->next; if(it->signame) free(it->signame); if(it->endsig) free(it->endsig); if(it->name) free(it->name); free(it); } void trainsim_cmd(char *cmd) { char *p; Train *t; Track *trk; int x, y, fl; char buff[256]; if(!strncmp(cmd, "log", 3)) { if(!flog) { if(!(flog = fopen("log", "w"))) do_alert(L("Cannot create log file.")); return; } fclose(flog); flog = 0; return; } if(!strncmp(cmd, "replay", 6)) { for(p = cmd + 6; *p == ' ' || *p == '\t'; ++p); sprintf(buff, "%s.log", p); if(!(frply = fopen(buff, "r"))) { do_alert(L("Cannot read log file.")); return; } /* replay commands are issued whenever the clock is updated */ return; } if(flog) fprintf(flog, "%ld,%s\n", current_time, cmd); if(!strncmp(cmd, "quit", 4)) main_quit_cmd(); else if(!strncmp(cmd, "about", 5)) about_dialog(); else if(!strncmp(cmd, "edit", 4)) { if(running) start_stop(); edit_cmd(); } else if(!strncmp(cmd, "noedit", 6)) noedit_cmd(); else if(!strncmp(cmd, "stationsched", 12)) station_sched_dialog(NULL); else if(!strncmp(cmd, "paths", 5)) create_path_window(); else if(!strncmp(cmd, "fast", 4)) { if(time_mults[cur_time_mult + 1] != -1) time_mult = time_mults[++cur_time_mult]; update_labels(); } else if(!strncmp(cmd, "slow", 4)) { if(cur_time_mult > 0) { time_mult = time_mults[--cur_time_mult]; update_labels(); } } else if(!strncmp(cmd, "t0", 2)) { if(cont(L("Do you want to restart the simulation?")) == ANSWER_YES) { if(!all_trains_everyday(schedule)) select_day_dialog(); fill_schedule(schedule, 0); sprintf(status_line, L("Simulation restarted.")); trainsim_init(); invalidate_field(); repaint_all(); } } else if(!strncmp(cmd, "speeds", 6)) { show_speeds = !show_speeds; invalidate_field(); repaint_all(); } else if(!strncmp(cmd, "traditional", 6)) { signal_traditional = !signal_traditional; invalidate_field(); repaint_all(); } else if(!strncmp(cmd, "graph", 6)) { create_tgraph(); } else if(!strncmp(cmd, "blocks", 6)) { show_blocks = !show_blocks; invalidate_field(); repaint_all(); } else if(!strncmp(cmd, "alert", 5)) { beep_on_alert = !beep_on_alert; } else if(!strncmp(cmd, "sched", 5)) { create_schedule(0); } else if(!strncmp(cmd, "run", 3)) { start_stop(); update_button("run", running ? L("Stop") : L("Start")); } else if(!strncmp(cmd, "newtrain", 8)) { create_train(); } else if(!strncmp(cmd, "greensigs", 9)) { open_all_signals(); } else if(!strncmp(cmd, "shunt", 5)) { cmd += 5; while(*cmd == ' ' || *cmd == '\t') ++cmd; if(!(t = findTrainNamed(cmd))) return; shunt_train(t); } else if(!strncmp(cmd, "traininfo", 9)) { cmd += 9; while(*cmd == ' ' || *cmd == '\t') ++cmd; if(!(t = findTrainNamed(cmd))) return; train_info_dialog(t); } else if(!strncmp(cmd, "decelerate", 10)) { long val; cmd += 10; while(*cmd == ' ' || *cmd == '\t') ++cmd; val = strtol(cmd, &cmd, 0); while(*cmd == ' ' || *cmd == '\t') ++cmd; if(!(t = findTrainNamed(cmd))) return; decelerate_train(t, val); } else if(!strncmp(cmd, "accelerate", 10)) { long val; cmd += 10; while(*cmd == ' ' || *cmd == '\t') ++cmd; val = strtol(cmd, &cmd, 0); while(*cmd == ' ' || *cmd == '\t') ++cmd; if(!(t = findTrainNamed(cmd))) return; accelerate_train(t, val); } else if(!strncmp(cmd, "stationinfo", 11)) { cmd += 11; while(*cmd == ' ' || *cmd == '\t') ++cmd; station_sched_dialog(cmd); } else if(!strncmp(cmd, "reverse", 7)) { cmd += 7; while(*cmd == ' ' || *cmd == '\t') ++cmd; if(!(t = findTrainNamed(cmd))) return; reverse_train(t); } else if(!strncmp(cmd, "new", 3)) { if(running) start_stop(); init_all(); } else if(!strncmp(cmd, "save ", 5)) { if(save_layout(cmd + 5, layout)) sprintf(status_line, "%s '%s.trk'.", L("Layout saved in file"), cmd + 5); repaint_labels(); } else if(!strncmp(cmd, "savegame ", 9)) { if(save_game(cmd + 9)) sprintf(status_line, "%s '%s.sav'.", L("Game status saved in file"), cmd + 9); repaint_labels(); } else if(!strncmp(cmd, "restore ", 8)) { restore_game(cmd + 8); repaint_all(); fill_schedule(schedule, 0); update_labels(); } else if(!strncmp(cmd, "open", 4) || !strncmp(cmd, "load", 4)) { fl = cmd[0] == 'o'; /* open vs. load */ cmd += 4; while(*cmd == ' ') ++cmd; strcpy(current_project, cmd); if(running) start_stop(); clean_trains(schedule); schedule = 0; invalidate_field(); if(!(layout = load_field(cmd))) { sprintf(status_line, "%s '%s.trk'", L("cannot load"), cmd); error(status_line); return; } if(!(schedule = load_trains(cmd))) error(L("No schedule for this territory!")); link_all_tracks(); link_signals(layout); if(fl && !all_trains_everyday(schedule)) select_day_dialog(); if(fl) check_delayed_entries(schedule); /* fill_schedule(schedule, 0); */ trainsim_init(); /* clear counters, timer */ repaint_all(); } else if(!strncmp(cmd, "click", 5)) { fl = cmd[5] == '1'; /* click1 vs. click */ for(cmd += 5 + fl; *cmd == ' ' || *cmd == '\t'; ++cmd); if(*cmd >= '0' && *cmd <= '9') { x = strtol(cmd, &cmd, 10); if(*cmd == ',') ++cmd; y = strtol(cmd, &cmd, 10); } else { if(!(trk = findItineraryNamed(cmd))) return; /* impossible ? */ x = trk->x; y = trk->y; } if(fl) track_selected1(x, y); else track_selected(x, y); } else if(!strcmp(cmd, "info")) { track_info_dialogue(); } else if(!strcmp(cmd, "performance")) { performance_dialog(); } else if(!strcmp(cmd, "itinerary")) { itinerary_cmd(); } else if(!strcmp(cmd, "options")) { options_dialog(); if(hard_counters) perf_vals = perf_hard; else perf_vals = perf_easy; invalidate_field(); repaint_all(); update_labels(); new_status_position(); } else { sprintf(status_line, "Command: %s", cmd); repaint_labels(); } }