3 * Micropolis, Unix Version. This game was released for the Unix platform
4 * in or about 1990 and has been modified for inclusion in the One Laptop
5 * Per Child program. Copyright (C) 1989 - 2007 Electronic Arts Inc. If
6 * you need assistance with this program, you may contact:
7 * http://wiki.laptop.org/go/Micropolis or email micropolis@laptop.org.
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or (at
12 * your option) any later version.
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details. You should have received a
18 * copy of the GNU General Public License along with this program. If
19 * not, see <http://www.gnu.org/licenses/>.
21 * ADDITIONAL TERMS per GNU GPL Section 7
23 * No trademark or publicity rights are granted. This license does NOT
24 * give you any right, title or interest in the trademark SimCity or any
25 * other Electronic Arts trademark. You may not distribute any
26 * modification of this program using the trademark SimCity or claim any
27 * affliation or association with Electronic Arts Inc. or its employees.
29 * Any propagation or conveyance of this program must include this
30 * copyright notice and these terms.
32 * If you convey this program (or any modifications of it) and assume
33 * contractual liability for the program to recipients of it, you agree
34 * to indemnify Electronic Arts for any liability that those contractual
35 * assumptions impose on Electronic Arts.
37 * You may not misrepresent the origins of this program; modified
38 * versions of the program must be marked as such and not identified as
39 * the original program.
41 * This disclaimer supplements the one included in the General Public
42 * License. TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
43 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
44 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK. THE ENTIRE RISK OF
45 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU. ELECTRONIC ARTS
46 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
47 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
48 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
49 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
50 * USAGE, OR TRADE PRACTICE. ELECTRONIC ARTS DOES NOT WARRANT AGAINST
51 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
52 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
53 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
54 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
55 * CORRECTED. NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
56 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME
57 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
58 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
59 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
65 #define filename2UNIX(name) \
66 { char *p; for (p = name; *p; p++) if (*p == '\\') *p = '/'; }
68 #define filename2UNIX(name) /**/
72 Tcl_Interp
*tk_mainInterp
= NULL
;
73 Tcl_CmdBuf buffer
= NULL
;
74 Tk_TimerToken sim_timer_token
= 0;
75 int sim_timer_idle
= 0;
76 int sim_timer_set
= 0;
78 int UpdateDelayed
= 0;
79 int AutoScrollEdge
= 16;
80 int AutoScrollStep
= 16;
81 int AutoScrollDelay
= 10;
82 Tk_TimerToken earthquake_timer_token
;
83 int earthquake_timer_set
= 0;
84 int earthquake_delay
= 3000;
85 int PerformanceTiming
;
90 #define DEF_VIEW_FONT "-Adobe-Helvetica-Bold-R-Normal-*-140-*"
92 Tk_ConfigSpec TileViewConfigSpecs
[] = {
93 {TK_CONFIG_FONT
, "-font", (char *) NULL
, (char *) NULL
,
94 DEF_VIEW_FONT
, Tk_Offset(SimView
, fontPtr
), 0},
95 {TK_CONFIG_STRING
, "-messagevar", (char *) NULL
, (char *) NULL
,
96 NULL
, Tk_Offset(SimView
, message_var
), 0},
97 {TK_CONFIG_PIXELS
, "-width", "width", "Width",
98 0, Tk_Offset(SimView
, width
), 0},
99 {TK_CONFIG_PIXELS
, "-height", "height", "Height",
100 0, Tk_Offset(SimView
, height
), 0},
101 {TK_CONFIG_END
, (char *) NULL
, (char *) NULL
, (char *) NULL
,
106 int TileViewCmd(CLIENT_ARGS
);
107 int ConfigureTileView(Tcl_Interp
*interp
, SimView
*view
,
108 int argc
, char **argv
, int flags
);
109 static void TileViewEventProc(ClientData clientData
, XEvent
*eventPtr
);
110 static void DestroyTileView(ClientData clientData
);
112 int ConfigureSimGraph(Tcl_Interp
*interp
, SimGraph
*graph
,
113 int argc
, char **argv
, int flags
);
115 static void MicropolisTimerProc(ClientData clientData
);
117 int SimCmd(CLIENT_ARGS
);
118 int DoEditorCmd(CLIENT_ARGS
);
119 int DoMapCmd(CLIENT_ARGS
);
120 int GraphViewCmd(CLIENT_ARGS
);
121 int DoGraphCmd(CLIENT_ARGS
);
122 int SpriteCmd(CLIENT_ARGS
);
123 extern int Tk_PieMenuCmd();
124 extern int Tk_IntervalCmd();
128 TileViewCmd(CLIENT_ARGS
)
130 Tk_Window tkwin
= (Tk_Window
) clientData
;
135 Tcl_AppendResult(interp
, "wrong # args: should be \"",
136 argv
[0], " pathName ?options?\"", (char *) NULL
);
140 if (strcmp(argv
[0], "editorview") == 0)
141 viewclass
= Editor_Class
;
142 else if (strcmp(argv
[0], "mapview") == 0)
143 viewclass
= Map_Class
;
148 tkwin
= Tk_CreateWindowFromPath(interp
, tkwin
,
149 argv
[1], (char *) NULL
);
154 view
= (SimView
*)ckalloc(sizeof (SimView
));
157 view
->interp
= interp
;
160 if (viewclass
== Editor_Class
) {
161 Tk_SetClass(view
->tkwin
, "EditorView");
163 Tk_CreateEventHandler(view
->tkwin
,
164 VisibilityChangeMask
|
166 StructureNotifyMask
|
170 TileViewEventProc
, (ClientData
) view
);
171 Tcl_CreateCommand(interp
, Tk_PathName(view
->tkwin
),
172 DoEditorCmd
, (ClientData
) view
, (void (*)()) NULL
);
174 Tk_SetClass(view
->tkwin
, "MapView");
176 Tk_CreateEventHandler(view
->tkwin
,
177 VisibilityChangeMask
|
179 StructureNotifyMask
/* |
182 PointerMotionMask */ ,
183 TileViewEventProc
, (ClientData
) view
);
184 Tcl_CreateCommand(interp
, Tk_PathName(view
->tkwin
),
185 DoMapCmd
, (ClientData
) view
, (void (*)()) NULL
);
188 Tk_MakeWindowExist(view
->tkwin
);
190 if (getenv("XSYNCHRONIZE") != NULL
) {
191 XSynchronize(Tk_Display(tkwin
), 1);
194 if (viewclass
== Editor_Class
) {
195 InitNewView(view
, "MicropolisEditor", Editor_Class
, EDITOR_W
, EDITOR_H
);
198 InitNewView(view
, "MicropolisMap", Map_Class
, MAP_W
, MAP_H
);
202 if (ConfigureTileView(interp
, view
, argc
-2, argv
+2, 0) != TCL_OK
) {
203 /* XXX: destroy view */
204 Tk_DestroyWindow(view
->tkwin
);
208 switch (view
->class) {
218 interp
->result
= Tk_PathName(view
->tkwin
);
224 ConfigureTileView(Tcl_Interp
*interp
, SimView
*view
,
225 int argc
, char **argv
, int flags
)
227 if (Tk_ConfigureWidget(interp
, view
->tkwin
, TileViewConfigSpecs
,
228 argc
, argv
, (char *) view
, flags
) != TCL_OK
) {
232 if (view
->class == Map_Class
) {
233 Tk_GeometryRequest(view
->tkwin
, MAP_W
, MAP_H
);
235 if (view
->width
|| view
->height
) {
236 Tk_GeometryRequest(view
->tkwin
, view
->width
, view
->height
);
239 EventuallyRedrawView(view
);
248 //fprintf(stderr, "InvalidateMaps\n");
249 for (view
= sim
->map
; view
!= NULL
; view
= view
->next
) {
252 EventuallyRedrawView(view
);
262 //fprintf(stderr, "InvalidateEditors\n");
263 for (view
= sim
->editor
; view
!= NULL
; view
= view
->next
) {
266 EventuallyRedrawView(view
);
276 //fprintf(stderr, "RedrawMaps\n");
278 for (view
= sim
->map
; view
!= NULL
; view
= view
->next
) {
280 EventuallyRedrawView(view
);
290 //fprintf(stderr, "RedrawEditors\n");
292 for (view
= sim
->editor
; view
!= NULL
; view
= view
->next
) {
294 EventuallyRedrawView(view
);
301 DisplayTileView(ClientData clientData
)
303 SimView
*view
= (SimView
*) clientData
;
304 Tk_Window tkwin
= view
->tkwin
;
308 view
->flags
&= ~VIEW_REDRAW_PENDING
;
309 if (view
->visible
&& (tkwin
!= NULL
) && Tk_IsMapped(tkwin
)) {
310 switch (view
->class) {
314 DoUpdateEditor(view
);
317 //fprintf(stderr, "DisplayTileView\n");
329 TileViewEventProc expose configure motion
337 EventuallyRedrawView(SimView
*view
)
339 if (!(view
->flags
& VIEW_REDRAW_PENDING
)) {
340 Tk_DoWhenIdle(DisplayTileView
, (ClientData
) view
);
341 view
->flags
|= VIEW_REDRAW_PENDING
;
347 CancelRedrawView(SimView
*view
)
349 if (view
->flags
& VIEW_REDRAW_PENDING
) {
350 Tk_CancelIdleCall(DisplayTileView
, (ClientData
) view
);
352 view
->flags
&= ~VIEW_REDRAW_PENDING
;
357 TileAutoScrollProc(ClientData clientData
)
359 SimView
*view
= (SimView
*)clientData
;
362 if (view
->tool_mode
!= 0) {
364 int result
, root_x
, root_y
, x
, y
;
365 unsigned int key_buttons
;
368 XQueryPointer(Tk_Display(view
->tkwin
), Tk_WindowId(view
->tkwin
),
369 &root
, &child
, &root_x
, &root_y
, &x
, &y
, &key_buttons
);
371 if (x
< AutoScrollEdge
)
372 dx
= -AutoScrollStep
;
373 else if (x
> (view
->w_width
- AutoScrollEdge
))
375 if (y
< AutoScrollEdge
)
376 dy
= -AutoScrollStep
;
377 else if (y
> (view
->w_height
- AutoScrollEdge
))
381 int px
= view
->pan_x
, py
= view
->pan_y
;
383 if (view
->tool_mode
== -1) {
387 DoPanBy(view
, dx
, dy
);
388 view
->tool_x
+= view
->pan_x
- px
;
389 view
->tool_y
+= view
->pan_y
- py
;
390 view
->auto_scroll_token
=
391 Tk_CreateTimerHandler(AutoScrollDelay
, TileAutoScrollProc
,
394 sprintf(buf
, "UIDidPan %s %d %d", Tk_PathName(view
->tkwin
), x
, y
);
402 TileViewEventProc(ClientData clientData
, XEvent
*eventPtr
)
404 SimView
*view
= (SimView
*) clientData
;
406 if ((eventPtr
->type
== Expose
) && (eventPtr
->xexpose
.count
== 0)) {
408 EventuallyRedrawView(view
);
409 } else if (eventPtr
->type
== MapNotify
) {
411 } else if (eventPtr
->type
== UnmapNotify
) {
413 } else if (eventPtr
->type
== VisibilityNotify
) {
414 if (eventPtr
->xvisibility
.state
== VisibilityFullyObscured
)
418 } else if (eventPtr
->type
== ConfigureNotify
) {
419 if (view
->class == Editor_Class
)
421 eventPtr
->xconfigure
.width
,
422 eventPtr
->xconfigure
.height
);
423 EventuallyRedrawView(view
);
424 } else if (eventPtr
->type
== DestroyNotify
) {
425 Tcl_DeleteCommand(view
->interp
, Tk_PathName(view
->tkwin
));
427 CancelRedrawView(view
);
428 Tk_EventuallyFree((ClientData
) view
, DestroyTileView
);
429 } else if ((view
->class == Editor_Class
) &&
430 (view
->show_me
!= 0) &&
431 ((eventPtr
->type
== EnterNotify
) ||
432 (eventPtr
->type
== LeaveNotify
) ||
433 (eventPtr
->type
== MotionNotify
))) {
434 int last_x
= view
->tool_x
, last_y
= view
->tool_y
,
435 last_showing
= view
->tool_showing
;
436 int x
, y
, showing
, autoscroll
;
438 if (eventPtr
->type
== EnterNotify
) {
440 x
= eventPtr
->xcrossing
.x
; y
= eventPtr
->xcrossing
.y
;
441 } else if (eventPtr
->type
== LeaveNotify
) {
443 x
= eventPtr
->xcrossing
.x
; y
= eventPtr
->xcrossing
.y
;
446 x
= eventPtr
->xmotion
.x
; y
= eventPtr
->xmotion
.y
;
449 if (view
->tool_mode
!= 0) {
451 if ((x
< AutoScrollEdge
) ||
452 (x
> (view
->w_width
- AutoScrollEdge
)) ||
453 (y
< AutoScrollEdge
) ||
454 (y
> (view
->w_height
- AutoScrollEdge
))) {
455 if (!view
->auto_scroll_token
) {
456 view
->auto_scroll_token
=
457 Tk_CreateTimerHandler(AutoScrollDelay
, TileAutoScrollProc
,
461 if (view
->auto_scroll_token
) {
462 Tk_DeleteTimerHandler(view
->auto_scroll_token
);
463 view
->auto_scroll_token
= 0;
468 ViewToPixelCoords(view
, x
, y
, &x
, &y
);
469 view
->tool_showing
= showing
;
471 if (view
->tool_mode
!= -1) {
472 view
->tool_x
= x
; view
->tool_y
= y
;
475 /* XXX: redraw all views showing cursor */
476 /* XXX: also, make sure switching tools works w/out moving */
477 if (((view
->tool_showing
!= last_showing
) ||
478 (view
->tool_x
!= last_x
) ||
479 (view
->tool_y
!= last_y
))) {
481 EventuallyRedrawView(view
);
491 DestroyTileView(ClientData clientData
)
493 SimView
*view
= (SimView
*) clientData
;
500 StdinProc(ClientData clientData
, int mask
)
503 static int gotPartial
= 0;
507 if (mask
& TK_READABLE
) {
508 if (fgets(line
, 200, stdin
) == NULL
) {
511 sim_exit(0); // Just sets tkMustExit and ExitReturn
514 Tk_DeleteFileHandler(0);
521 cmd
= Tcl_AssembleCmd(buffer
, line
);
527 result
= Tcl_RecordAndEval(tk_mainInterp
, cmd
, 0);
528 if (*tk_mainInterp
->result
!= 0) {
529 if ((result
!= TCL_OK
) || sim_tty
) {
530 printf("%s\n", tk_mainInterp
->result
);
542 StructureProc(ClientData clientData
, XEvent
*eventPtr
)
544 if (eventPtr
->type
== DestroyNotify
) {
551 DelayedMap(ClientData clientData
)
553 while (Tk_DoOneEvent(TK_IDLE_EVENTS
) != 0) {
554 /* Empty loop body. */
556 if (MainWindow
== NULL
) {
559 Tk_MapWindow(MainWindow
);
563 DidStopPan(SimView
*view
)
566 sprintf(buf
, "UIDidStopPan %s", Tk_PathName(view
->tkwin
));
573 MicropolisTimerProc(ClientData clientData
)
575 sim_timer_token
= NULL
;
584 StartMicropolisTimer();
586 StopMicropolisTimer();
592 ReallyStartMicropolisTimer(ClientData clientData
)
594 int delay
= sim_delay
;
595 XDisplay
*xd
= XDisplays
;
597 StopMicropolisTimer();
600 if ((NeedRest
> 0) ||
602 (xd
->tkDisplay
->buttonWinPtr
!= NULL
) ||
603 (xd
->tkDisplay
->grabWinPtr
!= NULL
)) {
604 if (ShakeNow
|| NeedRest
) {
605 if (delay
< 50000) delay
= 50000;
614 Tk_CreateMicroTimerHandler(
624 StartMicropolisTimer()
626 if (sim_timer_idle
== 0) {
629 ReallyStartMicropolisTimer
,
635 StopMicropolisTimer()
637 if (sim_timer_idle
!= 0) {
640 ReallyStartMicropolisTimer
,
645 if (sim_timer_token
!= 0) {
646 Tk_DeleteTimerHandler(sim_timer_token
);
657 StartMicropolisTimer(NULL
);
663 DelayedUpdate(ClientData clientData
)
665 //fprintf(stderr, "DelayedUpdate\n");
674 if (!UpdateDelayed
) {
676 Tk_DoWhenIdle(DelayedUpdate
, (ClientData
) NULL
);
685 if (earthquake_timer_set
) {
686 Tk_DeleteTimerHandler(earthquake_timer_token
);
688 earthquake_timer_set
= 0;
694 MakeSound("city", "Explosion-Low");
695 Eval("UIEarthQuake");
697 if (earthquake_timer_set
) {
698 Tk_DeleteTimerHandler(earthquake_timer_token
);
700 Tk_CreateTimerHandler(earthquake_delay
, (void (*)())StopEarthquake
, (ClientData
) 0);
701 earthquake_timer_set
= 1;
707 if (tk_mainInterp
!= NULL
) {
708 Eval("catch {DoStopMicropolis}");
715 int result
= Tcl_Eval(tk_mainInterp
, buf
, 0, (char **) NULL
);
716 if (result
!= TCL_OK
) {
717 char *errorinfo
= Tcl_GetVar(tk_mainInterp
, "errorInfo",
719 if (errorinfo
== NULL
) errorinfo
= "<no backtrace>";
720 fprintf(stderr
, "Micropolis: error in TCL code: %s\n%s\n",
721 tk_mainInterp
->result
, errorinfo
);
734 tk_mainInterp
= Tcl_CreateExtendedInterp();
737 /* XXX: Figure out Extended TCL */
739 tclAppLongname
= "Wish - Tk Shell";
740 tclAppVersion
= TK_VERSION
;
741 Tcl_ShellEnvInit (interp
, TCLSH_ABORT_STARTUP_ERR
,
743 0, NULL
, /* argv var already set */
744 fileName
== NULL
, /* interactive? */
745 NULL
); /* Standard default file */
748 MainWindow
= Tk_CreateMainWindow(tk_mainInterp
, FirstDisplay
, "Micropolis");
749 if (MainWindow
== NULL
) {
750 fprintf(stderr
, "%s\n", tk_mainInterp
->result
);
751 sim_really_exit(1); // Just sets tkMustExit and ExitReturn
753 Tk_SetClass(MainWindow
, "Tk");
754 Tk_CreateEventHandler(MainWindow
, StructureNotifyMask
,
755 StructureProc
, (ClientData
) NULL
);
756 /* Tk_DoWhenIdle(DelayedMap, (ClientData) NULL); */
758 Tk_GeometryRequest(MainWindow
, 256, 256);
759 border
= Tk_Get3DBorder(tk_mainInterp
, MainWindow
, None
, "gray75");
760 if (border
== NULL
) {
761 Tcl_SetResult(tk_mainInterp
, (char *) NULL
, TCL_STATIC
);
762 Tk_SetWindowBackground(MainWindow
,
763 WhitePixelOfScreen(Tk_Screen(MainWindow
)));
765 Tk_SetBackgroundFromBorder(MainWindow
, border
);
767 XSetForeground(Tk_Display(MainWindow
),
768 DefaultGCOfScreen(Tk_Screen(MainWindow
)),
769 BlackPixelOfScreen(Tk_Screen(MainWindow
)));
773 editor_command_init();
774 graph_command_init();
776 sprite_command_init();
782 Tcl_CreateCommand(tk_mainInterp
, "piemenu", Tk_PieMenuCmd
,
783 (ClientData
)MainWindow
, (void (*)()) NULL
);
784 Tcl_CreateCommand(tk_mainInterp
, "interval", Tk_IntervalCmd
,
785 (ClientData
)MainWindow
, (void (*)()) NULL
);
789 sprintf(initCmd
, "source %s/micropolis.tcl", ResourceDir
);
790 filename2UNIX(initCmd
);
792 sim_exit(1); // Just sets tkMustExit and ExitReturn
798 buffer
= Tcl_CreateCmdBuf();
801 Tk_CreateFileHandler(0, TK_READABLE
, StdinProc
, (ClientData
) 0);
806 sprintf(buf
, "UIStartMicropolis {%s} {%s} {%s}",
807 HomeDir
, ResourceDir
, HostName
);
809 if (Eval(buf
) != TCL_OK
) {
810 sim_exit(1); // Just sets tkMustExit and ExitReturn
826 if (buffer
!= NULL
) {
827 Tcl_DeleteCmdBuf(buffer
);
830 Tcl_DeleteInterp(tk_mainInterp
);