Mon Jun 3 15:21:17 PDT 2002
[netwalk.git] / netwalk.e
blobea40175aae8e9971fd8cdd42a136818ea8464b8c
1 --TODO: similar code: get_neighbour, is_surrounded. need TUPLEs?
2 class NETWALK
3 inherit DIR_CONSTANT; SDL_CONSTANT; COLOR_TABLE; CONFIG
4 creation make
5 feature
6 io_score : IO_SCORE
8 tile_server_top : INTEGER is 1
9 tile_server_bottom : INTEGER is 2
10 tile_normal : INTEGER is 3
11 tile_terminal : INTEGER is 4
13 board : ARRAY2[TILE]
15 width : INTEGER is
17 Result := current_setting.width
18 end
20 height : INTEGER is
22 Result := current_setting.height
23 end
25 allow_wrap : BOOLEAN is
27 Result := current_setting.wrap
28 end
30 current_preset : PRESET
31 current_setting : SETTING
33 use_setting(s : SETTING) is
35 !!current_setting.make
36 current_setting.copy(s)
37 end
39 servertopi : INTEGER is
41 Result := width // 2
42 end
43 servertopj : INTEGER is
45 Result := height // 2 + 1
46 end
48 font : TTF_FONT is
50 Result := config.bigfont
51 end
53 mainfont : TTF_FONT is
55 Result := config.mainfont
56 end
58 win_image : IMAGE is
59 once
60 !!Result.make
61 Result.render_string("Well Done", font, white)
62 end
64 seed : INTEGER
65 move_count : INTEGER
67 new_game_button : BUTTON
68 options_button : BUTTON
69 hs_button : BUTTON
70 quit_button : BUTTON
72 options_window : OPTIONS_WINDOW
73 newhs_window : NEWHS_WINDOW
74 hs_window : HS_WINDOW
76 widget_list : LINKED_LIST[WIDGET]
78 add_widget(w : WIDGET; x, y : INTEGER) is
80 widget_list.add_last(w)
81 w.put_xy(x, y)
82 end
84 elapsed_seconds : INTEGER
85 elapsed_ticks : INTEGER
86 last_ticks : INTEGER
88 make is
89 local
90 c : COMMAND
91 time : TIME
93 !!widget_list.make
94 time.update
95 seed := time.hour * 3600 + time.minute * 60 + time.second
96 !!rand.with_seed(seed)
97 ext_init
98 connected_pipe_color := green
99 disconnected_pipe_color := darkred
100 connected_terminal_color := cyan
101 disconnected_terminal_color := darkpurple
102 current_preset := config.get_preset(config.default_preset)
103 use_setting(current_preset.setting)
104 !!move_image.make
105 move_image.new_dummy
107 !!best_image.make
108 best_image.new_dummy
110 !!time_image.make
111 time_image.new_dummy
113 !!new_game_button.make("New Game")
114 new_game_button.put_size(80, 20)
115 !COMMAND_NEW_GAME!c.make(Current)
116 new_game_button.put_command(c, new_game_button.signal_activate)
117 add_widget(new_game_button, 550, 20)
119 !!options_button.make("Options")
120 options_button.put_size(80, 20)
121 !COMMAND_OPTIONS!c.make(Current)
122 options_button.put_command(c, options_button.signal_activate)
123 add_widget(options_button, 550, 60)
125 !!hs_button.make("High Scores")
126 hs_button.put_size(80, 20)
127 !COMMAND_SHOW_HS!c.make(Current)
128 hs_button.put_command(c, hs_button.signal_activate)
129 add_widget(hs_button, 550, 100)
131 !!quit_button.make("Quit")
132 quit_button.put_size(80, 20)
133 !COMMAND_QUIT!c.make(Current)
134 quit_button.put_command(c, quit_button.signal_activate)
135 add_widget(quit_button, 550, 140)
137 !!io_score.make
138 io_score.load(config.score_file)
139 io_score.into_preset_list(config.preset_list)
141 make_options_window
142 make_newhs_window
143 make_hs_window
145 new_game
146 main_loop
149 make_options_window is
150 local
151 c : COMMAND
153 !!options_window.make
154 !COMMAND_OPTIONS_OK!c.make(Current)
155 options_window.put_command(c, options_window.signal_activate)
156 !COMMAND_CLOSE_CURRENT!c.make(Current)
157 options_window.put_command(c, options_window.signal_cancel)
158 options_window.put_xy(200, 100)
159 options_window.put_preset_list(config.preset_list)
162 make_newhs_window is
163 local
164 c : COMMAND
166 !!newhs_window.make
167 !COMMAND_NEWHS_OK!c.make(Current)
168 newhs_window.put_command(c, newhs_window.signal_activate)
169 newhs_window.put_xy(200, 100)
172 make_hs_window is
173 local
174 c : COMMAND
176 !!hs_window.make
177 !COMMAND_CLOSE_CURRENT!c.make(Current)
178 hs_window.put_command(c, hs_window.signal_activate)
179 hs_window.put_xy(200, 100)
180 hs_window.init_scores(config.preset_list)
183 close_current_window is
184 require
185 current_window /= Void
187 current_window := Void
190 show_hs is
191 require
192 current_window = Void
194 current_window := hs_window
197 newhs_ok is
198 require
199 current_window = newhs_window
200 local
201 s : STRING
203 current_window := Void
204 s := newhs_window.name_tb.string
205 current_preset.hiscore.put_name(s)
206 hs_window.update_score(current_preset)
207 io_score.from_preset_list(config.preset_list)
208 io_score.save(config.score_file)
211 options_ok is
212 require
213 current_window = options_window
215 current_window := Void
216 options_window.update_setting
217 if current_preset /= options_window.preset then
218 current_preset := options_window.preset
219 use_setting(options_window.setting)
220 new_game
221 elseif not current_setting.is_equal(options_window.setting) then
222 use_setting(options_window.setting)
223 new_game
227 new_game is
229 rand.with_seed(seed)
230 !!board.make(1, width, 1, height)
231 generate_board
232 preprocess_board
233 reset_score
234 scramble_board
235 update_best_image
236 reset_score
237 game_state := state_playing
238 last_ticks := get_ticks
239 elapsed_seconds := 0
240 update_time_image
241 elapsed_ticks := 0
244 options is
246 current_window := options_window
247 options_window.put_info(current_preset, current_setting)
250 state_none : INTEGER is 0
251 state_playing : INTEGER is 1
252 state_victory : INTEGER is 2
253 state_quit : INTEGER is 3
254 game_state : INTEGER
256 current_window : WINDOW
258 main_loop is
259 local
260 e : EVENT
261 i : INTEGER
263 from
264 until game_state = state_quit
265 loop
266 seed := seed + 1
267 blank_screen
269 widget_list.do_all(agent {WIDGET}.update)
270 draw_border
271 if game_state = state_playing then
272 i := get_ticks
273 elapsed_ticks := elapsed_ticks + i - last_ticks
274 last_ticks := i
275 i := elapsed_ticks // 1000
276 if i > elapsed_seconds then
277 elapsed_seconds := i
278 update_time_image
281 draw_board
282 check_connections
283 if is_victorious then
284 game_state := state_victory
285 hiscore_check
288 if game_state = state_victory then
289 draw_board
290 win_image.blit(10, 0)
293 best_image.blit(10, 420)
294 move_image.blit(10, 440)
295 time_image.blit(10, 460)
297 if current_window /= Void then
298 current_window.update
301 ext_update_screen
302 e := poll_event
303 if e /= Void then
304 if current_window /= Void then
305 current_window.process_event(e)
306 else
307 handle_event(e)
313 hiscore_check is
314 local
315 newhi : BOOLEAN
316 score : INTEGER
317 hiscore : SCORE
319 score := move_count - best
320 if current_preset /= Void then
321 hiscore := current_preset.hiscore
322 if hiscore = Void then
323 newhi := True
324 elseif hiscore.time > elapsed_seconds then
325 newhi := True
326 elseif hiscore.time = elapsed_seconds and then hiscore.score > score then
327 newhi := True
329 if newhi then
330 !!hiscore.make("Anonymous", score, elapsed_seconds)
331 current_preset.put_hiscore(hiscore)
332 current_window := newhs_window
333 newhs_window.name_tb.put_string(hiscore.name)
338 quit is
340 game_state := state_quit
343 handle_event(e : EVENT) is
345 inspect e.type
346 when sdl_keydown then
347 inspect e.i1
348 when sdlk_f2 then
349 new_game
350 else
352 when sdl_mousebuttondown then
353 handle_mbdown(e)
354 when sdl_quit then
355 quit
356 else
360 handle_mbdown(e : EVENT) is
361 local
362 i, j : INTEGER
363 it : ITERATOR[WIDGET]
365 it := widget_list.get_new_iterator
366 from it.start
367 until it.is_off
368 loop
369 if it.item.contains(e.x, e.y) then
370 it.item.process_event(e)
372 it.next
374 if game_state = state_playing then
375 i := e.x // cellwidth
376 j := e.y // cellheight
377 if on_board(i, j) then
378 if e.i1 = 1 then
379 rotateccw(i, j)
380 else
381 rotatecw(i, j)
387 move_image : IMAGE
388 best_image : IMAGE
389 time_image : IMAGE
391 reset_score is
393 move_count := 0
394 move_image.free
395 move_image.render_string("moves: " + move_count.to_string, mainfont, white)
398 tally_move is
400 move_count := move_count + 1
401 move_image.free
402 move_image.render_string("moves: " + move_count.to_string, mainfont, white)
405 update_best_image is
407 best_image.free
408 best_image.render_string("par: " + best.to_string, mainfont, white)
411 update_time_image is
413 time_image.free
414 time_image.render_string("time: " + elapsed_seconds.to_string, mainfont, white)
417 rotatecw(i, j : INTEGER) is
418 local
419 t : TILE
420 dir : INTEGER
421 bak : BOOLEAN
423 t := board.item(i, j)
424 if t /= Void then
425 if t = server_top or else t = server_bottom then
426 bak := server_top.neighbour.item(1)
427 server_top.neighbour.put(server_bottom.neighbour.item(4), 1)
428 server_bottom.neighbour.put(server_bottom.neighbour.item(3), 4)
429 server_bottom.neighbour.put(server_bottom.neighbour.item(2), 3)
430 server_bottom.neighbour.put(bak, 2)
431 else
432 bak := t.neighbour.item(4)
433 from dir := 4
434 until dir = 1
435 loop
436 t.neighbour.put(t.neighbour.item(dir - 1), dir)
437 dir := dir - 1
439 t.neighbour.put(bak, dir)
441 tally_move
445 rotateccw(i, j : INTEGER) is
446 local
447 t : TILE
448 dir : INTEGER
449 bak : BOOLEAN
451 t := board.item(i, j)
452 if t /= Void then
453 if t = server_top or else t = server_bottom then
454 bak := server_top.neighbour.item(1)
455 server_top.neighbour.put(server_bottom.neighbour.item(2), 1)
456 server_bottom.neighbour.put(server_bottom.neighbour.item(3), 2)
457 server_bottom.neighbour.put(server_bottom.neighbour.item(4), 3)
458 server_bottom.neighbour.put(bak, 4)
459 else
460 bak := t.neighbour.item(1)
461 from dir := 1
462 until dir = 4
463 loop
464 t.neighbour.put(t.neighbour.item(dir + 1), dir)
465 dir := dir + 1
467 t.neighbour.put(bak, dir)
469 tally_move
473 server_top, server_bottom : TILE
475 preprocess_board is
476 local
477 i, j : INTEGER
478 t : TILE
480 from i := 1
481 until i > width
482 loop
483 from j := 1
484 until j > height
485 loop
486 t := board.item(i, j)
487 if t /= Void then
488 t.count_neighbours
490 j := j + 1
492 i := i + 1
496 draw_board is
497 local
498 i, j : INTEGER
500 from i := 1
501 until i > width
502 loop
503 from j := 1
504 until j > height
505 loop
506 draw_tile(board.item(i, j))
507 j := j + 1
509 i := i + 1
513 draw_border is
514 local
515 i : INTEGER
517 from i := 1
518 until i > width + 1
519 loop
520 fill_rect(i * cellwidth, cellheight, 1, height * cellheight, blue)
521 i := i + 1
524 from i := 1
525 until i > height + 1
526 loop
527 fill_rect(cellwidth, i * cellheight, width * cellwidth, 1, blue)
528 i := i + 1
532 cellwidth : INTEGER is 32
533 cellheight : INTEGER is 32
534 uppipex : INTEGER is 13
535 uppipew : INTEGER is 6
536 uppipeh : INTEGER is 19
537 leftpipey : INTEGER is 13
538 leftpipew : INTEGER is 19
539 leftpipeh : INTEGER is 6
541 check_connections is
542 local
543 check_list : LINKED_LIST[TILE]
544 i, j : INTEGER
545 dir : INTEGER
546 t, t2 : TILE
548 from i := 1
549 until i > width
550 loop
551 from j := 1
552 until j > height
553 loop
554 t := board.item(i, j)
555 if t /= Void then
556 t.disconnect
558 j := j + 1
560 i := i + 1
563 !!check_list.make
564 check_list.add_last(server_top)
565 check_list.add_last(server_bottom)
566 server_top.connect
567 server_bottom.connect
569 from
570 until check_list.is_empty
571 loop
572 t := check_list.first
573 check_list.remove_first
574 from dir := 1
575 until dir > 4
576 loop
577 if t.neighbour.item(dir) then
578 t2 := get_neighbour(t, dir)
579 if t2 /= Void and then
580 t2.neighbour.item(dir_opposite(dir)) and then
581 not t2.is_connected then
582 t2.connect
583 check_list.add_last(t2)
586 dir := dir + 1
591 is_victorious : BOOLEAN is
592 local
593 i, j : INTEGER
594 t : TILE
596 Result := True
597 from i := 1
598 until i > width or else not Result
599 loop
600 from j := 1
601 until j > height or else not Result
602 loop
603 t := board.item(i, j)
604 if t /= Void and then not t.is_connected then
605 Result := False
607 j := j + 1
609 i := i + 1
613 maybe_wrapx(i : INTEGER) : INTEGER is
615 Result := i
616 if allow_wrap then
617 if i < 1 then
618 Result := i + width
619 elseif i > width then
620 Result := i - width
625 maybe_wrapy(i : INTEGER) : INTEGER is
627 Result := i
628 if allow_wrap then
629 if i < 1 then
630 Result := i + height
631 elseif i > height then
632 Result := i - height
637 get_neighbour(t : TILE; dir : INTEGER) : TILE is
638 local
639 x, y : INTEGER
640 x1, y1 : INTEGER
642 if dir = dir_up then
643 y1 := -1
644 elseif dir = dir_down then
645 y1 := 1
648 if dir = dir_left then
649 x1 := -1
650 elseif dir = dir_right then
651 x1 := 1
653 x := maybe_wrapx(t.x + x1)
654 y := maybe_wrapy(t.y + y1)
655 if on_board(x, y) then
656 Result := board.item(x, y)
660 on_board(x, y : INTEGER) : BOOLEAN is
662 Result := x >= 1 and then x <= width
663 and then y >= 1 and then y <= height
666 connected_pipe_color : COLOR
667 disconnected_pipe_color : COLOR
668 connected_terminal_color : COLOR
669 disconnected_terminal_color : COLOR
671 draw_tile(tile : TILE) is
672 local
673 x : INTEGER
674 y : INTEGER
675 c, c2 : COLOR
677 if tile /= Void then
678 x := tile.x * cellwidth
679 y := tile.y * cellheight
680 if tile.is_connected then
681 c := connected_pipe_color
682 c2 := connected_terminal_color
683 else
684 c := disconnected_pipe_color
685 c2 := disconnected_terminal_color
688 if tile.neighbour.item(dir_up) then
689 fill_rect(x + uppipex, y, uppipew, uppipeh, c)
691 if tile.neighbour.item(dir_left) then
692 fill_rect(x, y + leftpipey, leftpipew, leftpipeh, c)
694 if tile.neighbour.item(dir_right) then
695 fill_rect(x + uppipex, y + leftpipey, leftpipew, leftpipeh, c)
697 if tile.neighbour.item(dir_down) then
698 fill_rect(x + uppipex, y + leftpipey, uppipew, uppipeh, c)
701 inspect tile.type
702 when tile_server_top then
703 fill_rect(x + 7, y + 7, cellwidth - 12, cellheight - 6, blue)
704 when tile_server_bottom then
705 fill_rect(x + 7, y, cellwidth - 12, cellheight - 6, blue)
706 when tile_terminal then
707 fill_rect(x + 8, y + 8, 16, 16, c2)
708 else
714 ext_fill_rect(x, y, w, h : INTEGER; c : INTEGER) is
715 external "C"
718 wrap_flag : BOOLEAN
720 open_list : LINKED_LIST[TILE]
722 rand : STD_RAND
724 generate_board is
725 local
726 i : INTEGER
727 t : TILE
729 --place server
730 !!server_bottom.make_server_bottom
731 place_tile(server_bottom, servertopi, servertopj + 1)
732 !!server_top.make_server_top
733 place_tile(server_top, servertopi, servertopj)
735 --generate maze
736 !!open_list.make
737 open_list.add_last(server_top)
738 open_list.add_last(server_bottom)
740 from
741 until open_list.is_empty
742 loop
743 rand.next
744 i := rand.last_integer(open_list.count)
745 t := open_list @ i
746 rand.next
747 i := rand.last_integer(4)
748 try_extend(t, i)
752 best : INTEGER
754 scramble_board is
755 local
756 i, j : INTEGER
757 sym2, sym4 : BOOLEAN
759 best := 0
760 from i := 1
761 until i > width
762 loop
763 from j := 1
764 until j > height
765 loop
766 if board.item(i, j) /= server_top then
767 sym2 := False
768 sym4 := False
769 if board.item(i, j) = server_bottom and then
770 server_top.neighbour.item(dir_up) = server_bottom.neighbour.item(dir_down) and then
771 server_bottom.neighbour.item(dir_left) = server_bottom.neighbour.item(dir_right) then
772 sym2 := True
773 if server_top.neighbour.item(dir_up) = server_bottom.neighbour.item(dir_right) then
774 sym4 := True
776 elseif board.item(i, j).is_symmetric then
777 sym2 := True
780 rand.next
781 inspect rand.last_integer(4)
782 when 1 then
783 rotateccw(i, j)
784 if not sym4 then
785 best := best + 1
787 when 2 then
788 rotatecw(i, j)
789 if not sym4 then
790 best := best + 1
792 when 3 then
793 rotatecw(i, j)
794 rotatecw(i, j)
795 if not sym2 then
796 best := best + 2
798 else
801 j := j + 1
803 i := i + 1
807 try_extend(t : TILE; dir : INTEGER) is
808 local
809 x, y : INTEGER
810 x1, y1 : INTEGER
811 t2 : TILE
812 i : INTEGER
814 if dir = dir_up then
815 y1 := -1
816 elseif dir = dir_down then
817 y1 := 1
820 if dir = dir_left then
821 x1 := -1
822 elseif dir = dir_right then
823 x1 := 1
826 x := maybe_wrapx(t.x + x1)
827 y := maybe_wrapy(t.y + y1)
829 if on_board(x, y) then
830 t2 := board.item(x, y)
831 if t = server_top and then (dir = dir_left or else dir = dir_right) then
832 elseif t2 /= Void then
833 else
834 !!t2.make
835 place_tile(t2, x, y)
836 t.neighbour.put(True, dir)
837 t2.neighbour.put(True, dir_opposite(dir))
838 open_list.add_last(t2)
840 from i := open_list.lower
841 until i > open_list.upper
842 loop
843 if is_surrounded(open_list @ i) then
844 open_list.remove(i)
845 else
846 i := i + 1
853 is_surrounded(t : TILE) : BOOLEAN is
854 local
855 dir : INTEGER
856 x, y : INTEGER
857 x1, y1 : INTEGER
858 count : INTEGER
860 Result := True
861 from dir := 1
862 until dir > 4
863 loop
864 if t.neighbour.item(dir) then
865 count := count + 1
867 y1 := 0
868 if dir = dir_up then
869 y1 := -1
870 elseif dir = dir_down then
871 y1 := 1
874 x1 := 0
875 if dir = dir_left then
876 x1 := -1
877 elseif dir = dir_right then
878 x1 := 1
881 x := maybe_wrapx(t.x + x1)
882 y := maybe_wrapy(t.y + y1)
884 if on_board(x, y) then
885 if board.item(x, y) = Void then
886 if t = server_top then
887 if dir /= dir_left and then dir /= dir_right then
888 Result := False
890 else
891 Result := False
895 dir := dir + 1
897 if count >= 3 then
898 Result := True
902 place_tile(tile : TILE; x, y : INTEGER) is
904 tile.put_xy(x, y)
905 board.put(tile, x, y)
908 poll_event : EVENT is
909 local
910 em : EVENTMAKER
912 Result := ext_poll_event(em)
915 ext_init is
916 external "C"
919 ext_poll_event(em : EVENTMAKER) : EVENT is
920 external "C" alias "ext_poll_event"
923 get_ticks : INTEGER is
924 external "C" alias "ext_get_ticks"
927 ext_update_screen is
928 external "C"
931 fill_rect(x, y, w, h : INTEGER; c : COLOR) is
933 --ext_fill_rect(x + offsetx, y + offsety, w, h, c.to_integer)
934 ext_fill_rect(x, y, w, h, c.to_integer)
937 blank_screen is
939 fill_rect(0, 0, 640, 480, black)