-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmazewar_bot.py
More file actions
4137 lines (3876 loc) · 84.7 KB
/
Copy pathmazewar_bot.py
File metadata and controls
4137 lines (3876 loc) · 84.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""
Mazewar Bot for PDP-10 ITS System
TODO:
Enable shooting the humans
Original IMLAC Source is reproduced below the Python code for reference
"""
import telnetlib
import time
import random
import sys
class ANSITerminal:
"""Simple ANSI/VT100 terminal emulator to track screen state"""
def __init__(self, width=80, height=24):
self.width = width
self.height = height
self.screen = [[' ' for _ in range(width)] for _ in range(height)]
self.cursor_x = 0
self.cursor_y = 0
self.escape_mode = False
self.escape_buffer = ""
def process_char(self, char):
"""Process a single character, handling ANSI escape sequences"""
if self.escape_mode:
self.escape_buffer += char
if char.isalpha() or char == '@':
self.handle_escape_sequence(self.escape_buffer)
self.escape_mode = False
self.escape_buffer = ""
elif char == '\x1b': # ESC character
self.escape_mode = True
self.escape_buffer = ""
elif char == '\r': # Carriage return
self.cursor_x = 0
elif char == '\n': # Line feed
self.cursor_y = min(self.cursor_y + 1, self.height - 1)
elif char == '\b': # Backspace
self.cursor_x = max(0, self.cursor_x - 1)
elif ord(char) >= 32: # Printable character
if self.cursor_y < self.height and self.cursor_x < self.width:
self.screen[self.cursor_y][self.cursor_x] = char
self.cursor_x += 1
if self.cursor_x >= self.width:
self.cursor_x = 0
self.cursor_y = min(self.cursor_y + 1, self.height - 1)
def handle_escape_sequence(self, seq):
"""Handle ANSI escape sequences"""
if not seq:
return
if seq[0] == '[': # CSI sequence
seq = seq[1:]
if not seq:
return
cmd = seq[-1]
params = seq[:-1]
if params:
try:
nums = [int(x) if x else 0 for x in params.split(';')]
except:
nums = []
else:
nums = []
if cmd == 'H' or cmd == 'f': # Cursor position
row = nums[0] - 1 if len(nums) > 0 and nums[0] > 0 else 0
col = nums[1] - 1 if len(nums) > 1 and nums[1] > 0 else 0
self.cursor_y = max(0, min(row, self.height - 1))
self.cursor_x = max(0, min(col, self.width - 1))
elif cmd == 'J': # Clear screen
if not nums or nums[0] == 2:
self.screen = [[' ' for _ in range(self.width)] for _ in range(self.height)]
elif cmd == 'K': # Clear line
if not nums or nums[0] == 0:
for x in range(self.cursor_x, self.width):
self.screen[self.cursor_y][x] = ' '
def process_string(self, data):
"""Process a string of data"""
for char in data:
self.process_char(char)
def get_screen_text(self):
"""Get current screen as text"""
return '\n'.join(''.join(row) for row in self.screen)
class MazewarBot:
# Deterministic spawn positions by player join order
# Note: Positions are in PROTOCOL coordinates (1-indexed)
# Maze coordinates are 0-indexed, so subtract 1
SPAWN_POSITIONS = {
1: (10, 3, 3), # Second player: protocol (10,3) = maze (9,2), facing 3=West
2: (10, 3, 3),
3: (1, 1, 3),
4: (3, 1, 2),
5: (10, 2, 3)
}
def __init__(self, host, port, username="BOT", game_dir="GAMES", verbose=True, player_number=1):
self.host = host
self.port = port
self.username = username
self.game_dir = game_dir
self.tn = None
self.terminal = ANSITerminal(80, 24)
self.verbose = verbose
# self.player_number = player_number # Which player are we? (0=first, 1=second, etc.)
self.player_id = 1 # default to 1
self.game_data = "" # responses back from the PDP-10 during game play
# Maze state - actual maze is 16 columns x 32 rows (per assembly code)
# Assembly: AND [17'] for X = 0-15, AND [37'] for Y = 0-31
self.maze_map = None # 2D array of the maze
self.maze_width = 16 # Actual playable maze width (assembly uses 0-15)
self.maze_height = 32
# Player state - will be set to known spawn position
self.pos_x = 0
self.pos_y = 0
self.facing = 0 # 0=North, 1=East, 2=South, 3=West
# Navigation
self.visited = set()
self.move_history = []
self.loop_counter = 0 #used to determine if we're in a loop
def log(self, message, level="INFO"):
"""Print log message if verbose mode enabled"""
if self.verbose:
print(f"[{level}] {message}")
def connect(self):
"""Establish telnet connection to PDP-10"""
self.log(f"Connecting to {self.host}:{self.port}...")
self.tn = telnetlib.Telnet(self.host, self.port)
time.sleep(1.5)
response = self.wait_for_response(2.0)
self.log(f"Connection established", "DEBUG")
# Send Control-Z to get ITS attention
self.tn.write(b'\x1a')
time.sleep(1.0)
response = self.wait_for_response(3.0)
self.log(f"Connected successfully")
def login(self):
"""Login to ITS system"""
self.log(f"Logging in as {self.username}...")
self.send_command(f":LOGIN {self.username}")
response = self.wait_for_response(3.0)
if response:
self.terminal.process_string(response)
time.sleep(0.5)
response = self.wait_for_response(1.0)
if response:
self.terminal.process_string(response)
self.log("Login complete")
def send_command(self, command):
"""Send a command followed by carriage return"""
self.tn.write(command.encode('latin-1') + b'\r')
if self.verbose:
self.log(f"Sent command: {command}", "DEBUG")
def send_key(self, key):
"""Send a single keypress"""
if isinstance(key, str):
self.tn.write(key.encode('latin-1'))
else:
self.tn.write(bytes([key]))
def read_and_update_screen(self):
"""Read from connection"""
try:
data = self.tn.read_very_eager().decode('latin-1', errors='ignore')
if data:
#self.terminal.process_string(data)
self.game_data = data
print("game data")
print(self.game_data)
return True
except Exception as e:
self.log(f"Error reading screen: {e}", "ERROR")
return False
def wait_for_response(self, timeout=2.0):
"""Wait for a response with timeout"""
time.sleep(0.2)
start = time.time()
response = ""
last_data_time = time.time()
while time.time() - start < timeout:
try:
chunk = self.tn.read_very_eager().decode('latin-1', errors='ignore')
if chunk:
response += chunk
last_data_time = time.time()
else:
idle_time = time.time() - last_data_time
if response and idle_time > 0.3:
break
time.sleep(0.05)
except:
break
return response
def parse_maze_from_binary(self, data):
"""Parse the maze from the binary loader data
The maze is 32 octal words (6 digits each = 18 bits).
Each word represents one row of the maze.
However, only the first 16 bits are used for the playable area (columns 0-15).
The last 2 bits (columns 16-17) are borders.
1 = wall, 0 = passage
"""
self.log("Parsing maze from binary data...", "DEBUG")
# The default maze from the assembly listing
# These are 32 octal words from the assembly code
default_maze_octal = [
0o177777, 0o106401, 0o124675, 0o121205, 0o132055, 0o122741, 0o106415, 0o124161,
0o121405, 0o135775, 0o101005, 0o135365, 0o121205, 0o127261, 0o120205, 0o106765,
0o124405, 0o166575, 0o122005, 0o107735, 0o120001, 0o135575, 0o105005, 0o125365,
0o125225, 0o121265, 0o105005, 0o135375, 0o100201, 0o135675, 0o110041, 0o177777
]
# Use the default maze
self.log("Using default maze from assembly listing", "INFO")
maze_words = default_maze_octal
# Convert the maze words to a 2D map
# Only use the first 16 bits (columns 0-15) for the playable maze
self.maze_map = []
for row_idx, word in enumerate(maze_words):
str_word = bin(word)[2:] #strings are easier to manipulate than bits
row = []
for bit_pos in range(16):
if str_word[bit_pos]=="1": # Store True = passable, False = wall
row.append(False)
else:
row.append(True)
self.maze_map.append(row)
# Set spawn position based on player number
if self.player_number in self.SPAWN_POSITIONS and self.SPAWN_POSITIONS[self.player_number] is not None:
spawn_info = self.SPAWN_POSITIONS[self.player_number]
protocol_x, protocol_y, self.facing = spawn_info
self.pos_x = protocol_x
self.pos_y = protocol_y
self.log(f"Using known spawn position for player {self.player_number}: protocol({protocol_x}, {protocol_y}) = maze({self.pos_x}, {self.pos_y}) facing {['N','E','S','W'][self.facing]}", "INFO")
else:
# Find a starting position
self.log(f"Unknown spawn position for player {self.player_number}, searching...", "WARN")
for y in range(1, 31):
for x in range(1, 15):
if self.maze_map[y][x]:
self.pos_x = x
self.pos_y = y
self.facing = 0
self.log(f"Using fallback starting position: ({x}, {y})", "INFO")
break
else:
continue
break
# Verify the spawn position is passable
if self.pos_x >= 16 or self.pos_y >= 32:
self.log(f"ERROR: Spawn position ({self.pos_x}, {self.pos_y}) is out of bounds!", "ERROR")
return False
if not self.maze_map[self.pos_y][self.pos_x]:
self.log(f"ERROR: Spawn position ({self.pos_x}, {self.pos_y}) is a wall!", "ERROR")
return False
self.log(f"Starting position: ({self.pos_x}, {self.pos_y}) facing {['North','East','South','West'][self.facing]}", "INFO")
return True
def start_mazewar(self):
"""Start the Mazewar game and parse the maze"""
self.log(f"Starting Mazewar from {self.game_dir}...")
self.send_command(":TCTYPE OIMLAC")
time.sleep(0.5)
self.wait_for_response(1.0)
# Change directory
self.send_command(f":CWD {self.game_dir}")
time.sleep(0.5)
self.wait_for_response(1.0)
# Launch game
self.log("Launching Mazewar...", "DEBUG")
self.send_command(":RUN MAZE C")
# Wait for game to load and capture all data
all_response = ""
for attempt in range(15):
time.sleep(1.0)
chunk = self.wait_for_response(1.0)
#print(chunk)
if chunk:
if self.username.upper() in chunk:
print("username found")
loc = chunk.find(self.username)
#print ("loc ",loc)
#print (ord(chunk[loc-2]),ord(chunk[loc-1]))
"""
; 004 -- ANNOUNCE NEW PLAYER
; <ID # OF NEW PLAYER>
; <6 CHARS OF ID NAME>
; <2 CHAR # OF HITS WITH 100 BIT ON> (HIGH ORDER 6 BITS THEN LOW ORDER 6 BITS)
; <2 CHAR # OF DEATHS WITH 100 BIT ON>"""
if ord(chunk[loc-2])==4: #message type 4, new player announcement
self.player_number=ord(chunk[loc-1])
print ("Player Number: ",self.player_number)
break
# Parse the maze (will use default if can't extract from data)
if not self.parse_maze_from_binary(all_response):
self.log("Failed to parse maze!", "ERROR")
return False
# Handle join prompts if needed
# if "Hang on" in all_response or "joining" in all_response.lower():
# self.log("Joining game...", "DEBUG")
# time.sleep(1)
self.wait_for_response(3.0)
# Send protocol messages to announce position
# This makes the bot appear in the game at the spawn position
self.log("Sending spawn position protocol messages...", "DEBUG")
self.send_spawn_message()
time.sleep(0.5)
self.wait_for_response(1.0)
self.log("Mazewar startup complete")
return True
def send_spawn_message(self):
"""Send Type 2 (PLAYER MOVED) message to announce spawn position
"""
# Type 2: Player Moved
self.tn.write(b'\x02')
time.sleep(1)
#self.tn.write(b'\x02')
# Player ID
self.tn.write(bytes([self.player_number]))
time.sleep(1)
# Direction with 0x40 added
direction_byte = self.facing + 0x40
self.tn.write(bytes([direction_byte]))
time.sleep(1)
# X location: convert from maze coords (0-indexed) to protocol coords (1-indexed)
protocol_x = self.pos_x
x_byte = protocol_x + 0x40
self.tn.write(bytes([x_byte]))
time.sleep(1)
# Y location: convert from maze coords (0-indexed) to protocol coords (1-indexed)
protocol_y = self.pos_y
y_byte = protocol_y + 0x40
self.tn.write(bytes([y_byte]))
time.sleep(1)
# 69 19
# End marker
#self.tn.write(b'\x11')
#self.tn.write(b'\x19')
#time.sleep(10)
self.log(f"Sent spawn message: ID={self.player_number}, Dir={self.facing}, Protocol=({protocol_x},{protocol_y}), Maze=({self.pos_x},{self.pos_y})", "DEBUG")
def can_move_forward(self):
"""Check if we can move forward from current position"""
dx, dy = [(0, -1), (1, 0), (0, 1), (-1, 0)][self.facing]
new_x = self.pos_x + dx
new_y = self.pos_y + dy
# Check bounds (16x32 maze)
if new_x < 0 or new_x >= 16 or new_y < 0 or new_y >= 32:
return False
return self.maze_map[new_y][new_x]
def move_forward(self):
"""Move forward (update internal state)"""
# wireshark indicates \x69 or ASCII i dec 105
key=103+self.player_number # i for player 2, j for player 3
self.send_key(chr(key))
# print("forward")
# print(key)
time.sleep(1)
# Then update our internal state
dx, dy = [(0, -1), (1, 0), (0, 1), (-1, 0)][self.facing]
self.pos_x += dx
self.pos_y += dy
if (self.pos_x, self.pos_y) in self.visited:
#already been here. Add to the consectutive visited spaces counter
self.loop_counter = self.loop_counter +1
else:
#haven't been here before, reset loop counter to 0
self.loop_counter = 0
self.visited.add((self.pos_x, self.pos_y))
self.move_history.append('F')
self.log(f"Moved to ({self.pos_x}, {self.pos_y})", "DEBUG")
def turn_left(self):
"""Turn left (update internal state)"""
#wireshark indicates 19 or 11 hex. guessing 19 (25 decimal)
#That's for the first player. Subsequent player is 1 more
key = 23+self.player_number
# print("left")
# print(key)
self.send_key(chr(key))
time.sleep(1)
# Then update internal state
self.facing = (self.facing - 1) % 4
self.move_history.append('L')
direction = ['North', 'East', 'South', 'West'][self.facing]
self.log(f"Turned left, now facing {direction}", "DEBUG")
def turn_right(self):
"""Turn right (update internal state)"""
#11 hex, 17 dec
key=15+self.player_number
# print("right")
# print(key)
self.send_key(chr(key))
time.sleep(1)
# Then update internal state
self.facing = (self.facing + 1) % 4
self.move_history.append('R')
direction = ['North', 'East', 'South', 'West'][self.facing]
self.log(f"Turned right, now facing {direction}", "DEBUG")
def explore_maze(self):
"""Simple right-hand wall following exploration"""
# Try to move according to right-hand rule:
# 1. Try to turn right and move
# 2. If can't, try to move forward
# 3. If can't, try to turn left
# 4. If can't, turn around
# except if the loop_counter says we've gone over the same cells already n times
if self.loop_counter < 5:
# First check right
self.turn_right()
if self.can_move_forward():
self.move_forward()
return "turned right and moved forward"
# Can't go right, try straight
self.turn_left() # Undo the right turn
if self.can_move_forward():
self.move_forward()
return "moved forward"
# Can't go straight, try left
self.turn_left()
if self.can_move_forward():
self.move_forward()
return "turned left and moved forward"
# Can't go left, turn around
self.turn_left()
if self.can_move_forward():
self.move_forward()
return "turned around and moved forward"
# Completely stuck (shouldn't happen in valid maze)
self.log("WARNING: Stuck in all directions!", "WARN")
return "stuck"
else:
self.log("possibly in a loop","INFO")
self.loop_counter = 0 # reset the loop counter
# this time we're preferentially going forward or turning left
# try straight
if self.can_move_forward():
self.move_forward()
return "moved forward"
# Can't go straight, try left
self.turn_left()
if self.can_move_forward():
self.move_forward()
return "turned left and moved forward"
# check right
self.turn_right() # undo left
self.turn_right()
if self.can_move_forward():
self.move_forward()
return "turned right and moved forward"
# Can't go left, turn around
self.turn_right() # second right turn to make a U
if self.can_move_forward():
self.move_forward()
return "turned around and moved forward"
# Completely stuck (shouldn't happen in valid maze)
self.log("WARNING: Stuck in all directions!", "WARN")
return "stuck"
def play(self, duration=300):
"""Main game loop with navigation"""
self.log(f"Starting autonomous play for {duration} seconds...")
if self.maze_map is None:
self.log("ERROR: No maze map loaded!", "ERROR")
return
start_time = time.time()
move_count = 0
# Mark starting position as visited
self.visited.add((self.pos_x, self.pos_y))
try:
while time.time() - start_time < duration:
# Read any data from server
self.read_and_update_screen()
# Have I been shot?
if len(self.game_data) > 1 and self.game_data[0] == "\x03": #should be shot
self.log("Oh no! I've been shot!","DEBUG")
#go back to the starting point and restart everything
spawn_info = self.SPAWN_POSITIONS[self.player_number]
protocol_x, protocol_y, self.facing = spawn_info
self.pos_x = protocol_x
self.pos_y = protocol_y
self.game_data = ""
self.visited.clear # reset the visited spaces
self.send_spawn_message() #go back to the original position
else:
# Make a move
action = self.explore_maze()
move_count += 1
self.log(f"Move {move_count}: {action} at ({self.pos_x},{self.pos_y}) facing {['N','E','S','W'][self.facing]}")
# Small delay between moves
time.sleep(0.5)
except KeyboardInterrupt:
self.log("\nBot stopped by user")
except Exception as e:
self.log(f"\nBot crashed: {e}", "ERROR")
import traceback
traceback.print_exc()
# Show stats
self.log(f"\n=== Navigation Stats ===")
self.log(f"Total moves: {move_count}")
self.log(f"Cells visited: {len(self.visited)}")
self.log(f"Current position: ({self.pos_x}, {self.pos_y})")
self.log(f"Facing: {['North', 'East', 'South', 'West'][self.facing]}")
# Graceful exit
self.graceful_exit()
self.log(f"\nBot finished.")
def graceful_exit(self):
"""Gracefully exit the game and ITS system"""
self.log("Performing graceful exit...", "DEBUG")
try:
# Send Control-Z and Bell as in original
self.tn.write(b'\x1a') # Control-Z
time.sleep(1)
self.tn.write(b'\x07') # Bell/beep (Control-G)
time.sleep(1)
self.send_command(":KILL")
time.sleep(0.5)
self.send_command(":LOGOUT")
time.sleep(0.5)
except Exception as e:
self.log(f"Error during graceful exit: {e}", "WARN")
def disconnect(self):
"""Close telnet connection"""
if self.tn:
self.tn.close()
self.log("Disconnected")
def main():
"""Example usage"""
HOST = "localhost"
PORT = 10003
USERNAME = "BOT" + str(random.randint(1,100))
PLAYER_NUMBER = 0 #will be given by the PDP-10 when the game starts
print("=== Mazewar PDP-10 Bot ===\n")
while True:
bot = MazewarBot(HOST, PORT, USERNAME, player_number=PLAYER_NUMBER)
try:
bot.connect()
bot.login()
if bot.start_mazewar():
bot.play(duration=600) # run 10 minutes
else:
print("Failed to start Mazewar!")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
finally:
bot.disconnect()
if __name__ == "__main__":
main()
"""
TITLE MAZE.3 GREG THOMPSON (GAT) 04/11/74
; MAZE
; Maze is a experiment in 3 dimensional graphics and intertask
; teleconferencing. It is a hunt and seek game that can involve up to
; eight Imlacs. The Imlac user is placed in a 16 by 32 square maze and
; attempts to hunt down and destroy the other inhabitents of the maze (the
; other Imlac users) before they do the same to him. Each player is
; represented by his uname (1 through 8 characters) as he moves through the
; maze. The various keys that are used to move through the maze and to
; fire are described below.
;
; UP ARROW - Move forward 1 square.
; DOWN ARROW - Back up one square.
; LEFT ARROW - Turn 90 degrees to the left.
; RIGHT ARROW- Turn 90 degrees to the right.
; FUNCTION 4 - Turn 180 degrees around.
; PAGE XMIT - Peek around the corner to the left.
; XMIT - Peek around the corner to the right.
; ESC - Fire.
; CTRL -Z - Exit maze program.
; FORM - Erase dispay buffer.
; FUNCTION 7 - Look at maze from top.
;
; The player enters the maze by typing MAZE<cr>; to monit or MAZE^k to
; DDT, while at an imlac. The screen will be blank for a minute or two
; while the imlac side of the maze program is loaded after which the player
; is placed in to the maze along with any other players. A letter on the
; top of the screen indicates the direction you are currently facing. The
; unames of the other players are listed on the sides followed their score
; and the number of times they were shot. Anytime a player is shot the
; bell will ring and an '!' will be placed next to the shooting players
; score and an '*' will be placed next to the number of times shot counter
; of the player that was just shot. Holding down the up or down arrow keys
; will cause them to repeat. After a shot is fired the player who is being
; shot at has two seconds to get out of view of the position that the
; shooting player was at at the time he fired the shot. All other
; characters typed are placed in a display buffer at the bottom of all the
; imlac's screen. Holding the Function-7 (or TAB as the case my be) will
; allow you to view your position in the maze from the top.
; The 3 buttons on the mouse and the 5 keyset buttons may be used as
; controls and have the following functions, starting from the left of the
; mouse; peek left, fire, peek right, turn around, turn left, move
; forward, turn right, and move backwards.
; Users may specify their own mazes if they are the first player in a
; maze by giving a file name after 'maze to use: '. Just a CR will default
; to the standard maze. User mazes must have a specific format if they are
; to be able to work. They must begin with a LOC 10020 followed by the label
; MAZE: on the first of 32. octal words which form a bit map for the
; maze. The maze must end with LOC 17713, JMP @.+1, 101, and an END.
; After assembling the maze must be imtraned by using the 'IMTRAN' command.
; A muddle function exists for printing out formated source mazes. It is
; initiated by floading 'imlac;maze print' in muddle and then issuing
; <PRINT-MAZE 'input file spec' 'output file spec'>$ where the output file
; spec defaults to the TTY. An example of a formated source maze is given
; below:
;.INSRT IMSRC;IMDEFS >
;
; LOC 10020'
;
; MAZE: 177777 ; HERE IS THE 32 WORD MAZE.
; 106401 ; NO FOUR SQUARES MAY BE EMPTY.
; 124675 ; AND SHARE A COMMON CORNER.
; 121205 ; ALL OUTSIDE WALLS MUST BE FILLED IN.
; 132055 ; THIS IS THE DEFAULT MAZE.
; 122741
; 106415
; 124161
; 121405
; 135775
; 101005
; 135365
; 121205
; 127261
; 120205
; 106765
; 124405
; 166575
; 122005
; 107735
; 120001
; 135575
; 105005
; 125365
; 125225
; 121265
; 105005
; 135375
; 100201
; 135675
; 110041
; 177777
;
; END 101' ; AUTO START BACK INTO CONSOLE PROGRAM
;
; Players start in random loctions.
; The current default maze is:
;
; N O R T H
;
;
; $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
; $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
; $$$ $$$$$$ $$$ $$$
; $$$ $$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$$$$ $$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$$$$ $$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$
; $$$ $$$$$$ $$$ $$$ $$$$$$ $$$
; $$$ $$$$$$ $$$ $$$ $$$$$$ $$$
; $$$ $$$ $$$ $$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$$$$$$$$$$ $$$
; $$$ $$$$$$ $$$ $$$$$$ $$$
; $$$ $$$$$$ $$$ $$$$$$ $$$
; $$$ $$$ $$$ $$$$$$$$$ $$$
; $$$ $$$ $$$ $$$$$$$$$ $$$
; $$$ $$$ $$$$$$ $$$ $$$
; $$$ $$$ $$$$$$ $$$ $$$
; $$$ $$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$ $$$
; $$$ $$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$
; $$$ $$$$$$$$$ $$$ $$$$$$$$$$$$ $$$ $$$
; $$$ $$$$$$$$$ $$$ $$$$$$$$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$
; W $$$ $$$ $$$$$$$$$ $$$ $$$$$$ $$$ E
; $$$ $$$ $$$$$$$$$ $$$ $$$$$$ $$$
; E $$$ $$$ $$$ $$$ $$$ A
; $$$ $$$ $$$ $$$ $$$
; S $$$ $$$$$$ $$$$$$$$$$$$$$$ $$$ $$$ S
; $$$ $$$$$$ $$$$$$$$$$$$$$$ $$$ $$$
; T $$$ $$$ $$$ $$$ $$$ $$$ T
; $$$ $$$ $$$ $$$ $$$ $$$
; $$$$$$$$$ $$$$$$ $$$ $$$$$$$$$$$$$$$ $$$
; $$$$$$$$$ $$$$$$ $$$ $$$$$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$$$$$$$$$$$$$$$$ $$$$$$$$$ $$$
; $$$ $$$$$$$$$$$$$$$$$$ $$$$$$$$$ $$$
; $$$ $$$ $$$
; $$$ $$$ $$$
; $$$ $$$$$$$$$ $$$$$$ $$$$$$$$$$$$$$$ $$$
; $$$ $$$$$$$$$ $$$$$$ $$$$$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$$$$$$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$$$$$$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$$$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$ $$$
; $$$ $$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$ $$$
; $$$ $$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$ $$$
; $$$ $$$ $$$
; $$$ $$$ $$$
; $$$ $$$$$$$$$ $$$$$$$$$ $$$$$$$$$$$$ $$$
; $$$ $$$$$$$$$ $$$$$$$$$ $$$$$$$$$$$$ $$$
; $$$ $$$ $$$ $$$
; $$$ $$$ $$$ $$$
; $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
; $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
;
;
; S O U T H
;
; MAZE PROTOCOL: MESSAGES ARE SENT TO ALL OTHER IMLACS
; DO NOT SEND TO ORIGINATING IMLAC
;
; 001 -- PLAYER LEAVES GAME
; <ID>
;
; 002 -- PLAYER MOVED
; <ID>
; <NEW DIRECTION WITH 100 BIT ON>
; <NEW XLOC WITH 100 BIT ON>
; <NEW YLOC WITH 100 BIT ON>
;
; 003 -- PLAYER DIED
; <ID>
; <ID OF WHO DIED>
;
; 004 -- ANNOUNCE NEW PLAYER
; <ID # OF NEW PLAYER>
; <6 CHARS OF ID NAME>
; <2 CHAR # OF HITS WITH 100 BIT ON> (HIGH ORDER 6 BITS THEN LOW ORDER 6 BITS)
; <2 CHAR # OF DEATHS WITH 100 BIT ON>
;
; 014 -- ERASE DISPLAY RING BUFFER
;
;
; IDS MUST BE >= 1 AND <= 8.
;
; ALL INCOMING MESSAGES ARE CHECKED FOR LEGALITY. BAD MESSAGES ARE FLUSHED.
; A NUMBER IN THE STATUS LINE INDICATES THE NUMBER OF BAD MESSAGES RECIEVED.
; INFORMATION CONCERNING THE LAST BAD MESSAGE RECIEVED IS SAVED FOR LATER EVALUATION.
;
; ALL CHARACTERS SUBROUTINES AND THE DJMS TABLE IS UP IN THE CONSOLE PROGRAM (SSV).
; THE DJMS TABLE IS ACCESSED THROUGH LOCATION 24 OCTAL WHICH STARTS WITH THE ENTRY
; FOR OCTAL CODE 40 (SPACE).
;
; ANY CHARACTERS TYPED ON CONSOLE (>014') ARE SENT TO PDP-10 AND SHOULD
; BE ECHOED TO ALL! CONSOLES INCLUDING THE ORIGINATOR.
;
; ANY OTHER CHARACTERS RECEIVED BY IMLAC ARE DISPLAYED IN A
; RING BUFFER AT THE BOTTOM OF THE PICTURE.
;
; THE FIRST ANNOUNCE NEW PLAYER MESSAGE THE IMLAC RECIEVES DEFINES ITS ID.
;
; THIS VERSION REQUIRES A GRAPHICS IMLAC WITH LONG VECTOR HARDWARE
; MULTI-LEVEL SUBROUTINING, AND 8K DISPLAY ADDRESSING MOD.
;
; THE MESSAGE SWITCHING PROGRAM ON THE 10 MUST ALSO KEEP TRACK
; OF THE CURRENT SCORES OF ALL THE PLAYERS SO WHEN A NEW PLAYER
; JOINS INTO A ALREADY EXISTING GAME HE MAY RECIEVE THE CURRECT
; SCORES OF ALL THE PLAYERS.
;
; WHEN AN IMLAC WANTS TO JOIN AN EXISTING MAZE THE FOLLOWING OCCURS:
; 1) THE MAZE PROGRAM IS LOADED INTO HIS IMLAC.
; 2) THE CURRENT MAZE IS LOADED ON TOP OF THE DEFAULT MAZE
; IF THE DEFAULT MAZE IS NOT BEING USED.
; 3) A TYPE 4 MESSAGE IS SENT TO ALL IMLACS ANOUNCING THE
; NEW IMLAC. THE NEW IMLAC GETS HIS ID FROM THIS MESSAGE.
; 4) TYPE 4 MESSAGES FOR ALL THE OTHER PLAYERS ARE SENT TO
; THE NEW IMLAC.
;
; WRITTEN BY:
;
; HOWARD PALMER STANFORD ORIGINAL IDEA & STAND-ALONE VERSION OF MAZE
; STEVE COLLEY CAL TECH ORIGINAL IDEA OF MAZE, STAND-ALONE MAZE
; & CRUDE MULTIPLE PLAYERS
; GREG THOMPSON M.I.T. FULL MULTIPLE PLAYERS ADDITIONS
; DAVE LEBLING M.I.T. PDP-10 MESSAGE SWITCHER AND ROBOTS
; KEN HARRENSTEIN M.I.T. PDP-10 part of fast interaction protocol
; CHARLES FRANKSTON M.I.T. IMLAC part of fast interaction protocol.
; actual program begins here
.INSRT IMSRC;IMDEFS >
; To keep midas from barfing 'RES' at use of these syms in prg.
IF1 EXPUNGE FIX,MOVE,PTR,EXP
FAST==1 ; to assemble fast-protocol version.
CHEAT==0 ; conditional to assemble cheater stuff
.MLLIT==1
LOC 10000'
.ADDR.=1 ; 8k addressing for display opcodes
JMP START ; starting point
JMP RESTART ; restarting entry point
JMP LEAVE ; entry to return to ssv (on error)
JMP RETN ; reenter maze main loop
; auto increment register definitions
DPTR=10' ; index loc 10 used as pointer
VISPT=11'
VISPT2=12'
VISPT3=13'
VISPT4=14'
VISPT5=15'
; index location 16' and 17' used by interupt routine
LOC 10020'
; here is the 32 word maze: no four squares may be empty and share a
; common corner, and all outside walls must be filled in
MAZE: 177777 ? 106401 ? 124675 ? 121205 ? 132055 ? 122741 ? 106415 ? 124161
121405 ? 135775 ? 101005 ? 135365 ? 121205 ? 127261 ? 120205 ? 106765
124405 ? 166575 ? 122005 ? 107735 ? 120001 ? 135575 ? 105005 ? 125365
125225 ? 121265 ? 105005 ? 135375 ? 100201 ? 135675 ? 110041 ? 177777
; here to wait for the loader signal
LOADER: RSF
JMP .-1
CLA
RRC
AND [177]
SAM [^A]
JMP LOADER
RSF
JMP .-1
CLA
RRC
AND [177]
SAM [^A]
JMP LOADER
JMP @[40]
; dstat, dx, dy, dir is my position and point into info table
DSTAT: 0 ; status flag
DX: 0 ; x position of this imlac
DY: 0 ; y position of this imlac
; start out big so we won't show up on map
DIR: 0 ; direction he is pointing
; bits 14 and 15 have meaning
; bit 14,bit 15
; 0 0 north
; 0 1 east
; 1 0 south
; 1 1 west
NEXTBIT:0
ETEM: 0
WPTR: 0
WPTR2: 0
CNT: 0 ; counters
CNT2: 0
KILL: 0 ; last player killed by this imlac
PTR4: 0 ; pointers
PTR3: 0
PTR2: 0
PTR: 0
XDELTA: 0
YDELTA: 0
BEAMBIT:0
LASTRIG:0
LASTLEF:0
HALLNGTH:0
MYREAL: 0 ; the real id of this imlac
MYBIT: 0 ; the id of this imlac
MYBIT1: 0 ; mybit-1 (normalize to 0-7)
IID: 0 ; temporary imlac id used for see routine
MPTR: 0
BIT: 0
; keyboard and keyset input data
KEY: 0 ; last key read in
KEYSET: 0 ; last value from keyset
KSCNT: 0 ; keyset repeat counter
HOME: 1372' ; ctrl z [exits program]
BACKUP: 204' ; down arrow (backup one square)
RTURN: 205' ; right arrow (turn 90 degrees right)
MOVE: 206' ; up arrow (move forward one square)
LTURN: 210' ; left arrow (turn 90 degrees left)
TURNA: 234' ; function 4 (turn 180 degrees around)
PEEKR: 202' ; xmit (peek to the right)
FIRE: 233' ; esc (fire)
PEEKL: 216' ; page xmit (peek to the left)
ERING: 214' ; form (erase ring buffer)
TOPVW: 211' ; tab (get a top view of maze)
TEM1: 0 ; temporarys
TEM2: 0
TEM3: 0
TOPSW: -1 ; indicates whether a top or inside view
IFN FAST,[