Import ztornado by Sophie Frühling
[ztornado.git] / tornado.inf
1 ! Z-Tornado - Two player weather action game
2 !
3 ! Tornado is copyright (C) 2000-2002  Oliver Feiler
4 ! http://www.lionking.org/~kiza/linux/tornado
5 ! Inform version copyright (c) 2003  Sophie Frühling (sfruehling@aon.at)
6
7 ! This program is free software; you can redistribute it and/or modify
8 ! it under the terms of the GNU General Public License as published by
9 ! the Free Software Foundation; either version 2 of the License, or
10 ! (at your option) any later version.
11 !
12 ! This program is distributed in the hope that it will be useful,
13 ! but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ! GNU General Public License for more details.
16 !
17 ! You should have received a copy of the GNU General Public License
18 ! along with this program; if not, write to the Free Software
19 ! Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 Release 2;
22
23 ! Screen stuff
24 Global screen_height;
25 Global screen_width;
26 Global dialog_x; ! dialog box cursor for player input 
27 Global cloud_x;
28 ! Can be switched off in terps where colour doesn't work properly
29 ! (For all I know only useful in Unix Frotz 2.32, where timed input
30 ! doesn't work like it should either)
31 Global colour = 1;
32
33 ! Player stuff
34 ! 1 if current player, otherwise 0
35 Global player1;
36 Global player2;
37 Array player1_name -> 18;
38 Array player2_name -> 18;
39 Global player1_ai = 0;
40 Global player2_ai = 0;
41 Global player1_left = 100;
42 Global player2_left = 100;
43 ! Can handle scores up to 9999999, which should be more than enough
44 Array player1_score -> 7;
45 Array player2_score -> 7;
46
47 ! House stuff
48 ! Position relative to screen
49 Global house1_x;
50 Global house1_y;
51 Global house2_x;
52 Global house2_y;
53 ! How many spaces does the house initially contain?
54 Constant SPACES = 32;
55
56 ! Weather stuff
57 Global windspeed;
58 Global randseed = 0;
59 ! 80 x 24 grid, discounting 1 row for statusline and 4 for cloud
60 Array snow_world -> 1520;
61 ! Actual entries of snow_world
62 Global fields = 0;
63
64 ! Highscores
65 Array highscore_file string "ZTORNADO.HGH";
66 Array highscore_array -> 220;
67
68 ! Add degree character to Zcharacter table
69 Zcharacter table + '@{B0}';
70
71 Include "draw.inf";
72 Include "erwin.inf";
73 Include "scores.inf";
74 Include "weather.inf";
75
76 [ Main  input;
77   if (TestTerp() == 0) return;
78   DrawTitleScreen();
79   InitHighScores();
80   while ((input = Menu()) ~= 'Q') {
81     switch (input) {
82       'A':
83         About();
84       'C':
85         if (colour) colour = 0;
86         else colour = 1;
87       'I':
88         Instructions();
89       'P':
90         Game();
91     }
92   }
93   Quit();
94 ];
95
96 [ Menu  ch;
97   @erase_window -1;
98   print "^^^";
99   Banner();
100   print "----------------------^";
101   print "(A) About Z-Tornado^",
102         "(C) Toggle colour (currently ";
103         if (colour) print "on";
104         else print "off";
105   print ")^",
106         "(I) Instructions^",
107         "(P) Play Z-Tornado^",
108         "(Q) Quit^^";
109   while (1) {
110     ch = Read();
111     switch (ch) {
112       'A', 'C', 'I', 'P', 'Q':
113         return ch;
114     }
115   }
116 ];
117
118 [ Game; Init(); Tornado(); Bye(); ];
119
120 Array score_array->7;
121 [ Tornado  quitting tmp_score;
122   while (1) {
123     InitGame();
124     while (1) {
125       @erase_window 1;
126       DrawSnowWorld();
127       DrawCloud(0);
128       windspeed = random(11) - 6;
129       if (player1) {
130         player1 = 0; player2 = 1;
131         quitting = Play(player2_ai);
132       } else {
133         player2 = 0; player1 = 1;
134         quitting = Play(player1_ai);
135       }
136       if (player1_left <= 0) {
137         tmp_score = 100
138           - ((CountDestroyed(' ', house2_x, house2_y, 17, 10) - SPACES)*100)/138;
139         CopyArray(score_array, player2_score);
140         Multiply(player2_score, score_array, tmp_score);
141         break;
142       }
143       if (player2_left <= 0) {
144         tmp_score = 100
145           - ((CountDestroyed(' ', house1_x, house1_y, 17, 10) - SPACES)*100)/138;
146         CopyArray(score_array, player1_score);
147         Multiply(player1_score, score_array, tmp_score);
148         break;
149       }
150       if (quitting) return;
151     }
152     DrawStatusLine();
153     AddToHighScore(player1_name, player1_score);
154     AddToHighScore(player2_name, player2_score);
155     if (ContinueGame() == 'N')
156       return;
157   }
158 ];
159
160 [ Play ai  x input aim destroyed;
161   DrawStatusLine();
162   ! We have a human player
163   if (ai == 0) {
164     while (1) {
165       input = PlayerChoice();
166       if (input == 'Q')
167         rtrue;
168       ! lightning: player doesn't have to aim
169       ! tornado: we'll draw the tornado cloud before aiming
170       if (input == 'L' or 'T')
171         break;
172       if (input == 'H' or 'R' or 'S') {
173         aim = GetAim();
174         break;
175       }
176       if (input == 'C')
177         ShowCurrentScores();
178       else if (input == 'O')
179         ShowHighScores();
180     } 
181   } else { ! The computer chooses
182     if (player1)
183       input = ErwinChoice(player1_left);
184     else
185       input = ErwinChoice(player2_left);
186     aim = ErwinAim(input);
187   }
188   destroyed = 0;
189   switch (input) {
190     'H':
191       x = cloud_x + random(14) - 1;
192       destroyed = DrawWeather(x, windspeed + aim, ':', 2);
193     'R':
194       x = cloud_x + random(14) - 1;
195       destroyed = DrawWeather(x, windspeed + aim, '/', 1);
196     'S':
197       x = cloud_x + random(14) - 1;
198       DrawSnow(x, windspeed + aim);
199     'L':
200       destroyed = DrawLightning(cloud_x - 7 + random(25));
201     'T':
202       DrawTornadoCloud();
203       if (ai == 0) aim = GetAim();
204       else Pause(2); ! So the player can see the cloud for a while
205       destroyed = DrawTornado(cloud_x, windspeed + aim);
206   }
207   player2_left = 100
208     - ((CountDestroyed(' ', house2_x, house2_y, 17, 10)
209     + CountSnow(house2_x, house2_y, 17, 10) - SPACES)*100)/138;
210   player1_left = 100
211     - ((CountDestroyed(' ', house1_x, house1_y, 17, 10)
212     + CountSnow(house1_x, house1_y, 17, 10) - SPACES)*100)/138;
213   ! (You get points for destroying your own house, too)
214   if (player1) {
215     CopyArray(score_array, player1_score);
216     AddShort(player1_score, score_array, destroyed*destroyed);
217   } else {
218     CopyArray(score_array, player2_score);
219     AddShort(player2_score, score_array, destroyed*destroyed);
220   }
221   DrawStatusLine();
222   rfalse;
223 ];
224
225 [ PlayerChoice  ch;
226   DrawUserInterface();
227   DrawStatusLine();
228   while (1) {
229     ch = Read();
230     switch (ch) {
231       'S', 'H', 'T', 'Q', 'R', 'L', 'C', 'O':
232         ClearUserInterface();
233         DrawSnowWorld();
234         return ch;
235     }
236   }
237 ];
238
239 [ GetAim  num;
240   DrawDialogWindow();
241   DrawStatusLine();
242   @set_cursor 8 dialog_x;
243   print "Aim?";
244   @set_cursor 9 dialog_x;
245   num = ReadNumber();
246   @set_cursor screen_height 1;
247   ClearDialogWindow();
248   DrawSnowWorld();
249   return num;
250 ];
251
252 ! Get the position of x y in snow_world array
253 [ GetLoc x y;
254   if (x < 1 || x > screen_width || y < 5 || y > screen_height)
255     rfalse;
256   if (y == 5) x--;
257   return (y - 5)*screen_width + x;
258 ];
259
260 [ CountDestroyed ch x y wid hgt  i j n count;
261   for (i = x: i < x + wid: i++) {
262     for (j = y: j < y + hgt: j++) {
263        n = GetLoc(i, j);
264        if (snow_world->n == ch)
265          count++;
266     }
267   }
268   return count;
269 ];
270
271 [ CountSnow xx yy wid hgt;
272   return CountDestroyed('*', xx, yy, wid, hgt);
273 ];
274
275 [ ChangeWorld x y num  i;
276   for (i = 0: i < num: i++)
277     ChangeSnowWorld(' ', x + i, y);
278 ];
279
280 [ ChangeSnowWorld ch xx yy  x;
281   x = GetLoc(xx, yy);
282   if (x < fields)
283     snow_world->x = ch;
284 ];
285
286 ! ----------- Initialising and game ending stuff -------------
287
288 [ TestTerp;
289   screen_width = 0->33;
290   screen_height = 0->32;
291   if (screen_width < 64)
292     print "Your interpreter detected a screen width of only ",
293           screen_width, ".^";
294   if (screen_height < 24)
295     print "Your interpreter detected a screen height of only ",
296           screen_height, ".^";
297   if (screen_width < 64 || screen_height < 24) {
298     print "To play Tornado you need at least 64 x 24.^^";
299     rfalse;
300   }
301   if (screen_width > 80) screen_width = 80;
302   screen_height = 24;
303   if ((0->1) & 128 == 0) {
304     print "Your interpreter apparently doesn't support timed events.",
305           " This game should be pretty tiresome to play without, sorry.^";
306     Read();
307   }
308   if ($32-->0 == 0) {
309     print "Your interpreter doesn't obey the Z-Machine standard. Z-Tornado",
310           " will probably not work correctly on such an interpreter.^";
311     Read();
312   }
313 ];
314
315 [ Init  y;
316   y = 0->32;
317   dialog_x = screen_width/2 - 16;
318   @split_window y;
319   @set_window 1;
320   if (colour) @set_colour 9 2;
321   house1_x = 5;
322   house1_y = screen_height - 10;
323   house2_x = screen_width - 21;
324   house2_y = screen_height - 10;
325   fields = screen_height*screen_width - 5*screen_width;
326   InitSnowWorld();
327   @erase_window 1;
328   DrawSnowWorld();
329   DrawCloud(10);
330   player1_ai = 0;
331   player2_ai = 0;
332   GetNames();
333 ];
334
335 ! There might be some bug in there, because Windows Frotz 2002 doesn't draw
336 ! the dialog window correctly after asking for the first player's name
337 [ GetNames  i;
338   DrawDialogWindow();
339   for (i = 2: i < 17: i++) {
340     player1_name->i = ' ';
341     player2_name->i = ' ';
342   }
343   @set_cursor 8 dialog_x;
344   print "Please enter your name Player 1";
345   @set_cursor 9 dialog_x;
346   player1_name->0 = 15;
347   read player1_name 0;
348   if (player1_name->1 == 0) {
349     ("Computer").print_to_array(player1_name);
350     player1_ai = 1;
351   }
352   @set_cursor 8 dialog_x;
353   print "Please enter your name Player 2";
354   @set_cursor 9 dialog_x;
355   print "               ";
356   @set_cursor 9 dialog_x;
357   player2_name->0 = 15;
358   read player2_name 0;
359   if (player2_name->1 == 0) {
360     ("Computer").print_to_array(player2_name);
361     player2_ai = 1;
362   }
363   @set_cursor screen_height 1;
364 ];
365
366 [ InitGame;
367   @erase_window 1;
368   player1_left = 100;
369   player2_left = 100;
370   player1 = 0;
371   player2 = 1;
372   Nullify(player1_score);
373   Nullify(player2_score);
374   InitSnowWorld();
375   DrawSnowWorld();
376   DrawCloud(10);
377 ];
378
379 [ InitSnowWorld  i;
380   for (i = 0: i < fields: i++)
381     snow_world->i = ' ';
382   InitHouse(0);
383   InitHouse(1);
384 ];
385
386 Array building table
387   "          ___    "
388   "          |||    "
389   "  /-------'|`-@@92  "
390   "/'/////////////`@@92"
391   "|---------------|"
392   "|-,_____,--,__,-|"
393   "|-|__|__|--|++|-|"
394   "|-|__|__|--|o+|-|"
395   "|----------|++|-|"
396   "|__________|__|_|";
397
398 Array temp_array -> 20;
399 [ InitHouse nr  i j x y tmp;
400   if (nr) {
401     x = house1_x;
402     y = house1_y;
403   } else {
404     x = house2_x;
405     y = house2_y;
406   }
407   temp_array->0 = 17;
408   for (j = 1: j <= 10: j++) {
409     tmp = GetLoc(x, y + j - 1);
410     (building-->j).print_to_array(temp_array);
411     for (i = 2: i < 19: i++)
412       snow_world->tmp++ = temp_array->i;
413   }
414 ];
415   
416 [ ContinueGame  yesno;
417   DrawDialogWindow();
418   DrawWinningScreen();
419   yesno = Read();
420   while (yesno ~= 'Y' && yesno ~= 'N')
421     yesno = Read();
422   ClearDialogWindow();
423   return yesno;
424 ];
425
426 [ Bye  y;
427   @erase_window 1;
428   y = screen_height - 2;
429   @set_cursor y 1;
430   @set_colour 1 1;
431   style roman;
432   @erase_window -1;
433 ];
434
435 [ Quit  x;
436   @erase_window -1;
437   print "^^[Trying to save highscore file]^";
438   @save highscore_array 220 highscore_file -> x;
439   if (x == 0)
440     print "^[Couldn't save the highscore file, sorry.]^";
441   print "^^Thanks for playing Tornado!^^";
442 ];
443
444 ! ---------------- Low level routines ---------------------
445
446 ! Read in a character/digit from the keyboard
447 [ Read  x k;
448   if (randseed > 0)
449     @read_char 1 x;
450   else {
451     ! This should make the random function more random.
452     @read_char 1 1 SeedRand -> k; 
453     for (k = 0: k < randseed: k++)
454       random(1); 
455   }
456   ! Convert lower case to upper case so we only have to test
457   ! for half of the cases
458   if ('a' <= x && x <= 'z')
459     return x - ('a' - 'A');
460   return x;
461 ];
462
463 [ SeedRand; randseed++; rfalse; ];
464
465 ! Read in numbers from -99 to 999 (We really only need -9 to 9)
466 ! If the player doesn't type a number, or anything, we return 0.
467 Array num_array->6;
468 [ ReadNumber  x y neg tens sum;
469   num_array->0 = 3;
470   read num_array 0;
471   if (num_array->1 == 0)
472     return 0;
473   x = 2;
474   if (num_array->2 == '-') {
475     x++;
476     neg = 1;
477   }
478   for (sum = 0: x < num_array->1 + 2: x++) {
479     if (num_array->x >= '0' && num_array->x <= '9') {
480       for (tens = 1, y = num_array->1: y + 1 > x: y--)
481         tens = tens * 10;
482       sum = sum + (num_array->x - '0') * tens;
483     }
484     else break;
485   }
486   if (neg) sum = -sum;
487   return sum;
488 ];
489
490 ! Pauses for x/10 seconds
491 [ Pause x  t;
492   @read_char 1 x Interrupt -> t;
493 ];
494
495 [ Interrupt; rtrue; ];