-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patha1.py
More file actions
880 lines (726 loc) · 26.7 KB
/
a1.py
File metadata and controls
880 lines (726 loc) · 26.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
"""A1: Raccoon Raiders game objects (all tasks)
CSC148, Winter 2022
This code is provided solely for the personal and private use of students
taking the CSC148 course at the University of Toronto. Copying for purposes
other than this use is expressly prohibited. All forms of distribution of this
code, whether as given or with any changes, are expressly prohibited.
Authors: Diane Horton, Sadia Sharmin, Dina Sabie, Jonathan Calver, and
Sophia Huynh.
All of the files in this directory and all subdirectories are:
Copyright (c) 2022 Diane Horton, Sadia Sharmin, Dina Sabie, Jonathan Calver,
and Sophia Huynh.
=== Module Description ===
This module contains all of the classes necessary for a1_game.py to run.
"""
from __future__ import annotations
from random import shuffle
from typing import List, Tuple, Optional
# Each raccoon moves every this many turns
RACCOON_TURN_FREQUENCY = 20
# Directions dx, dy
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
DIRECTIONS = [LEFT, UP, RIGHT, DOWN]
def get_shuffled_directions() -> List[Tuple[int, int]]:
"""
Provided helper that returns a shuffled copy of DIRECTIONS.
You should use this where appropriate
"""
to_return = DIRECTIONS[:]
shuffle(to_return)
return to_return
class GameBoard:
"""A game board on which the game is played.
=== Public Attributes ===
ended:
whether this game has ended or not
turns:
how many turns have passed in the game
width:
the number of squares wide this board is
height:
the number of squares high this board is
=== Representation Invariants ===
turns >= 0
width > 0
height > 0
No tile in the game contains more than 1 character, except that a tile
may contain both a Raccoon and an open GarbageCan.
=== Sample Usage ===
See examples in individual method docstrings.
"""
# === Private Attributes ===
# _player:
# the player of the game
# TODO Task #1 add any other private attribute(s) you need to keep track
# of the Characters on this board.
ended: bool
turns: int
width: int
height: int
_player: Optional[Player]
def __init__(self, w: int, h: int) -> None:
"""Initialize this Board to be of the given width <w> and height <h> in
squares. A board is initially empty (no characters) and no turns have
been taken.
>>> b = GameBoard(3, 3)
>>> b.width == 3
True
>>> b.height == 3
True
>>> b.turns == 0
True
>>> b.ended
False
"""
self.ended = False
self.turns = 0
self.width = w
self.height = h
self._player = None
# TODO Task #1 initialize any other private attributes you added.
def place_character(self, c: Character) -> None:
"""Record that character <c> is on this board.
This method should only be called from Character.__init__.
The decisions you made about new private attributes for class GameBoard
will determine what you do here.
Preconditions:
- c.board == self
- Character <c> has not already been placed on this board.
- The tile (c.x, c.y) does not already contain a character, with the
exception being that a Raccoon can be placed on the same tile where
an unlocked GarbageCan is already present.
Note: The testing will depend on this method to set up the board,
as the Character.__init__ method calls this method.
>>> b = GameBoard(3, 2)
>>> r = Raccoon(b, 1, 1) # when a Raccoon is created, it is placed on b
>>> b.at(1, 1)[0] == r # requires GameBoard.at be implemented to work
True
"""
# TODO Task #1
def at(self, x: int, y: int) -> List[Character]:
"""Return the characters at tile (x, y).
If there are no characters or if the (x, y) coordinates are not
on the board, return an empty list.
There may be as many as two characters at one tile,
since a raccoon can climb into a garbage can.
Note: The testing will depend on this method to allow us to
access the Characters on your board, since we don't know how
you have chosen to store them in your private attributes,
so make sure it is working properly!
>>> b = GameBoard(3, 2)
>>> r = Raccoon(b, 1, 1)
>>> b.at(1, 1)[0] == r
True
>>> p = Player(b, 0, 1)
>>> b.at(0, 1)[0] == p
True
"""
# TODO Task #1
def to_grid(self) -> List[List[chr]]:
"""
Return the game state as a list of lists of chrs (letters) where:
'R' = Raccoon
'S' = SmartRaccoon
'P' = Player
'C' = closed GarbageCan
'O' = open GarbageCan
'B' = RecyclingBin
'@' = Raccoon in GarbageCan
'-' = Empty tile
Each inner list represents one row of the game board.
>>> b = GameBoard(3, 2)
>>> _ = Player(b, 0, 0)
>>> _ = Raccoon(b, 1, 1)
>>> _ = GarbageCan(b, 2, 1, True)
>>> b.to_grid()
[['P', '-', '-'], ['-', 'R', 'C']]
"""
# TODO Task #1
def __str__(self) -> str:
"""
Return a string representation of this board.
The format is the same as expected by the setup_from_grid method.
>>> b = GameBoard(3, 2)
>>> _ = Raccoon(b, 1, 1)
>>> print(b)
---
-R-
>>> _ = Player(b, 0, 0)
>>> _ = GarbageCan(b, 2, 1, False)
>>> print(b)
P--
-RO
>>> str(b)
'P--\\n-RO'
"""
# TODO Task #1
def setup_from_grid(self, grid: str) -> None:
"""
Set the state of this GameBoard to correspond to the string <grid>,
which represents a game board using the following chars:
'R' = Raccoon not in a GarbageCan
'P' = Player
'C' = closed GarbageCan
'O' = open GarbageCan
'B' = RecyclingBin
'@' = Raccoon in GarbageCan
'-' = Empty tile
There is a newline character between each board row.
>>> b = GameBoard(4, 4)
>>> b.setup_from_grid('P-B-\\n-BRB\\n--BB\\n-C--')
>>> str(b)
'P-B-\\n-BRB\\n--BB\\n-C--'
"""
lines = grid.split("\n")
width = len(lines[0])
height = len(lines)
self.__init__(width, height) # reset the board to an empty board
y = 0
for line in lines:
x = 0
for char in line:
if char == 'R':
Raccoon(self, x, y)
elif char == 'S':
SmartRaccoon(self, x, y)
elif char == 'P':
Player(self, x, y)
elif char == 'O':
GarbageCan(self, x, y, False)
elif char == 'C':
GarbageCan(self, x, y, True)
elif char == 'B':
RecyclingBin(self, x, y)
elif char == '@':
GarbageCan(self, x, y, False)
Raccoon(self, x, y) # always makes it a Raccoon
# Note: the order mattered above, as we have to place the
# Raccoon BEFORE the GarbageCan (see the place_character
# method precondition)
x += 1
y += 1
# a helper method you may find useful in places
def on_board(self, x: int, y: int) -> bool:
"""Return True iff the position x, y is within the boundaries of this
board (based on its width and height), and False otherwise.
"""
return 0 <= x <= self.width - 1 and 0 <= y <= self.height - 1
def give_turns(self) -> None:
"""Give every turn-taking character one turn in the game.
The Player should take their turn first and the number of turns
should be incremented by one. Then each other TurnTaker
should be given a turn if RACCOON_TURN_FREQUENCY turns have occurred
since the last time the TurnTakers were given their turn.
After all turns are taken, check_game_end should be called to
determine if the game is over.
Precondition:
self._player is not None
>>> b = GameBoard(4, 3)
>>> p = Player(b, 0, 0)
>>> r = Raccoon(b, 1, 1)
>>> b.turns
0
>>> for _ in range(RACCOON_TURN_FREQUENCY - 1):
... b.give_turns()
>>> b.turns == RACCOON_TURN_FREQUENCY - 1
True
>>> (r.x, r.y) == (1, 1) # Raccoon hasn't had a turn yet
True
>>> (p.x, p.y) == (0, 0) # Player hasn't had any inputs
True
>>> p.record_event(RIGHT)
>>> b.give_turns()
>>> (r.x, r.y) != (1, 1) # Raccoon has had a turn!
True
>>> (p.x, p.y) == (1, 0) # Player moved right!
True
"""
# TODO Task #2 make the player take a turn by adding
# a line of code here
self.turns += 1 # PROVIDED, DO NOT CHANGE
if self.turns % RACCOON_TURN_FREQUENCY == 0: # PROVIDED, DO NOT CHANGE
pass # TODO Task #4 replace pass with code here to make each
# raccoon take a turn
self.check_game_end() # PROVIDED, DO NOT CHANGE
def handle_event(self, event: Tuple[int, int]) -> None:
"""Handle a user-input event.
The board's Player records the event that happened, so that when the
Player gets a turn, it can make the move that the user input indicated.
"""
self._player.record_event(event)
def check_game_end(self) -> Optional[int]:
"""Check if this game has ended. A game ends when all the raccoons on
this game board are either inside a can or trapped.
If the game has ended:
- update the ended attribute to be True
- Return the score, where the score is given by:
(number of raccoons trapped) * 10 + the adjacent_bin_score
If the game has not ended:
- update the ended attribute to be False
- return None
>>> b = GameBoard(3, 2)
>>> _ = Raccoon(b, 1, 0)
>>> _ = Player(b, 0, 0)
>>> _ = RecyclingBin(b, 1, 1)
>>> b.check_game_end() is None
True
>>> b.ended
False
>>> _ = RecyclingBin(b, 2, 0)
>>> b.check_game_end()
11
>>> b.ended
True
"""
# TODO Task #3 (you can leave calculating the score until Task #5)
r_num = 0
for char in self._character:
if isinstance(char, Raccoon):
r_num += 1
inside_num = 0
if char.inside_can:
inside_num += 1
outside_num = 0
else:
outside_num += 1
game_end = False
if r_num == inside_num or r_num == outside_num:
game_end = True
if game_end:
self.ended = True
return score
else:
self.ended = False
return None
def adjacent_bin_score(self) -> int:
"""
Return the size of the largest cluster of adjacent recycling bins
on this board.
Two recycling bins are adjacent when they are directly beside each other
in one of the four directions (up, down, left, right).
See Task #5 in the handout for ideas if you aren't sure how
to approach this problem.
>>> b = GameBoard(3, 3)
>>> _ = RecyclingBin(b, 1, 1)
>>> _ = RecyclingBin(b, 0, 0)
>>> _ = RecyclingBin(b, 2, 2)
>>> print(b)
B--
-B-
--B
>>> b.adjacent_bin_score()
1
>>> _ = RecyclingBin(b, 2, 1)
>>> print(b)
B--
-BB
--B
>>> b.adjacent_bin_score()
3
>>> _ = RecyclingBin(b, 0, 1)
>>> print(b)
B--
BBB
--B
>>> b.adjacent_bin_score()
5
"""
# TODO Task #5
class Character:
"""A character that has (x,y) coordinates and is associated with a given
board.
This class is abstract and should not be directly instantiated.
NOTE: To reduce the amount of documentation in subclasses, we have chosen
not to repeat information about the public attributes in each subclass.
Remember that the attributes are not inherited, but only exist once we call
the __init__ of the parent class.
=== Public Attributes ===
board:
the game board that this Character is on
x, y:
the coordinates of this Character on the board
=== Representation Invariants ===
x, y are valid coordinates in board (i.e. board.on_board(x, y) is True)
"""
board: GameBoard
x: int
y: int
def __init__(self, b: GameBoard, x: int, y: int) -> None:
"""Initialize this Character with board <b>, and
at tile (<x>, <y>).
When a Character is initialized, it is placed on board <b>
by calling the board's place_character method. Refer to the
preconditions of place_character, which must be satisfied.
"""
self.board = b
self.x, self.y = x, y
self.board.place_character(self) # this associates self with the board!
def move(self, direction: Tuple[int, int]) -> bool:
"""
Move this character to the tile
(self.x + direction[0], self.y + direction[1]) if possible. Each child
class defines its own version of what is possible.
Return True if the move was successful and False otherwise.
"""
raise NotImplementedError
def get_char(self) -> chr:
"""
Return a single character (letter) representing this Character.
"""
raise NotImplementedError
# Note: You can safely ignore PyCharm's warning about this class
# not implementing abstract method(s) from its parent class.
class TurnTaker(Character):
"""
A Character that can take a turn in the game.
This class is abstract and should not be directly instantiated.
"""
def take_turn(self) -> None:
"""
Take a turn in the game. This method must be implemented in any subclass
"""
raise NotImplementedError
class RecyclingBin(Character):
"""A recycling bin in the game.
=== Sample Usage ===
>>> rb = RecyclingBin(GameBoard(4, 4), 2, 1)
>>> rb.x, rb.y
(2, 1)
"""
def move(self, direction: Tuple[int, int]) -> bool:
"""Move this recycling bin to tile:
(self.x + direction[0], self.y + direction[1])
if possible and return whether or not this move was successful.
If the new tile is occupied by another RecyclingBin, push
that RecyclingBin one tile away in the same direction and take
its tile (as described in the Assignment 1 handout).
If the new tile is occupied by any other Character or if it
is beyond the boundaries of the board, do nothing and return False.
Precondition:
direction in DIRECTIONS
>>> b = GameBoard(4, 2)
>>> rb = RecyclingBin(b, 0, 0)
>>> rb.move(UP)
False
>>> rb.move(DOWN)
True
>>> b.at(0, 1) == [rb]
True
"""
# TODO Task #2
def get_char(self) -> chr:
"""
Return the character 'B' representing a RecyclingBin.
"""
return 'B'
class Player(TurnTaker):
"""The Player of this game.
=== Sample Usage ===
>>> b = GameBoard(3, 1)
>>> p = Player(b, 0, 0)
>>> p.record_event(RIGHT)
>>> p.take_turn()
>>> (p.x, p.y) == (1, 0)
True
>>> g = GarbageCan(b, 0, 0, False)
>>> p.move(LEFT)
True
>>> g.locked
True
"""
# === Private Attributes ===
# _last_event:
# The direction corresponding to the last keypress event that the user
# made, or None if there is currently no keypress event left to process
_last_event: Optional[Tuple[int, int]]
def __init__(self, b: GameBoard, x: int, y: int) -> None:
"""Initialize this Player with board <b>,
and at tile (<x>, <y>)."""
TurnTaker.__init__(self, b, x, y)
self._last_event = None
def record_event(self, direction: Tuple[int, int]) -> None:
"""Record that <direction> is the last direction that the user
has specified for this Player to move. Next time take_turn is called,
this direction will be used.
Precondition:
direction is in DIRECTIONS
"""
self._last_event = direction
def take_turn(self) -> None:
"""Take a turn in the game.
For a Player, this means responding to the last user input recorded
by a call to record_event.
"""
if self._last_event is not None:
self.move(self._last_event)
self._last_event = None
def move(self, direction: Tuple[int, int]) -> bool:
"""Attempt to move this Player to the tile:
(self.x + direction[0], self.y + direction[1])
if possible and return True if the move is successful.
If the new tile is occupied by a Racooon, a locked GarbageCan, or if it
is beyond the boundaries of the board, do nothing and return False.
If the new tile is occupied by a movable RecyclingBin, the player moves
the RecyclingBin and moves to the new tile.
If the new tile is unoccupied, the player moves to that tile.
If a Player attempts to move towards an empty, unlocked GarbageCan, the
GarbageCan becomes locked. The player's position remains unchanged in
this case. Also return True in this case, as the Player has performed
the action of locking the GarbageCan.
Precondition:
direction in DIRECTIONS
>>> b = GameBoard(4, 2)
>>> p = Player(b, 0, 0)
>>> p.move(UP)
False
>>> p.move(DOWN)
True
>>> b.at(0, 1) == [p]
True
>>> _ = RecyclingBin(b, 1, 1)
>>> p.move(RIGHT)
True
>>> b.at(1, 1) == [p]
True
"""
# TODO Task #2
def get_char(self) -> chr:
"""
Return the character 'P' representing this Player.
"""
return 'P'
class Raccoon(TurnTaker):
"""A raccoon in the game.
=== Public Attributes ===
inside_can:
whether or not this Raccoon is inside a garbage can
=== Representation Invariants ===
inside_can is True iff this Raccoon is on the same tile as an open
GarbageCan.
=== Sample Usage ===
>>> r = Raccoon(GameBoard(11, 11), 5, 10)
>>> r.x, r.y
(5, 10)
>>> r.inside_can
False
"""
inside_can: bool
def __init__(self, b: GameBoard, x: int, y: int) -> None:
"""Initialize this Raccoon with board <b>, and
at tile (<x>, <y>). Initially a Raccoon is not inside
of a GarbageCan, unless it is placed directly inside an open GarbageCan.
>>> r = Raccoon(GameBoard(5, 5), 5, 10)
"""
self.inside_can = False
# since this raccoon may be placed inside an open garbage can,
# we need to initially set the inside_can attribute
# BEFORE calling the parent init, which is where the raccoon is actually
# placed on the board.
TurnTaker.__init__(self, b, x, y)
def check_trapped(self) -> bool:
"""Return True iff this raccoon is trapped. A trapped raccoon is
surrounded on 4 sides (diagonals don't matter) by recycling bins, other
raccoons (including ones in garbage cans), the player, and/or board
edges. Essentially, a raccoon is trapped when it has nowhere it could
move.
Reminder: A racooon cannot move diagonally.
>>> b = GameBoard(3, 3)
>>> r = Raccoon(b, 2, 1)
>>> _ = Raccoon(b, 2, 2)
>>> _ = Player(b, 2, 0)
>>> r.check_trapped()
False
>>> _ = RecyclingBin(b, 1, 1)
>>> r.check_trapped()
True
"""
up_pos = self.x + UP[0], self.y + UP[1]
down_pos = self.x + DOWN[0], self.y + DOWN[1]
left_pos = self.x + LEFT[0], self.y + LEFT[1]
right_pos = self.x + RIGHT[0], self.y + RIGHT[1]
count = 0
if len(self.board.at(up_pos)) != 0 or (not self.board.on_board(up_pos)):
count += 1
if len(self.board.at(down_pos)) != 0 or (not self.board.on_board(down_pos)):
count += 1
if len(self.board.at(left_pos)) != 0 or (not self.board.on_board(left_pos)):
count += 1
if len(self.board.at(right_pos)) != 0 or (not self.board.on_board(right_pos)):
count += 1
return count == 4
def move(self, direction: Tuple[int, int]) -> bool:
"""Attempt to move this Raccoon in <direction> and return whether
or not this was successful.
If the tile one tile over in that direction is occupied by the Player,
a RecyclingBin, or another Raccoon, OR if the tile is not within the
boundaries of the board, do nothing and return False.
If the tile is occupied by an unlocked GarbageCan that has no Raccoon
in it, this Raccoon moves there and we have two characters on one tile
(the GarbageCan and the Raccoon). If the GarbageCan is locked, this
Raccoon uses this turn to unlock it and return True.
If a Raccoon is inside of a GarbageCan, it will not move. Do nothing and
return False.
Return True if the Raccoon unlocks a GarbageCan or moves from its
current tile.
Precondition:
direction in DIRECTIONS
>>> b = GameBoard(4, 2)
>>> r = Raccoon(b, 0, 0)
>>> r.move(UP)
False
>>> r.move(DOWN)
True
>>> b.at(0, 1) == [r]
True
>>> g = GarbageCan(b, 1, 1, True)
>>> r.move(RIGHT)
True
>>> r.x, r.y # Raccoon didn't change its position
(0, 1)
>>> not g.locked # Raccoon unlocked the garbage can!
True
>>> r.move(RIGHT)
True
>>> r.inside_can
True
>>> len(b.at(1, 1)) == 2 # Raccoon and GarbageCan are both at (1, 1)!
True
"""
# TODO Task #4
def take_turn(self) -> None:
"""Take a turn in the game.
If a Raccoon is in a GarbageCan, it stays where it is.
Otherwise, it randomly attempts (if it is not blocked) to move in
one of the four directions, with equal probability.
>>> b = GameBoard(3, 4)
>>> r1 = Raccoon(b, 0, 0)
>>> r1.take_turn()
>>> (r1.x, r1.y) in [(0, 1), (1, 0)]
True
>>> r2 = Raccoon(b, 2, 1)
>>> _ = RecyclingBin(b, 2, 0)
>>> _ = RecyclingBin(b, 1, 1)
>>> _ = RecyclingBin(b, 2, 2)
>>> r2.take_turn() # Raccoon is trapped
>>> r2.x, r2.y
(2, 1)
"""
# TODO Task #4
def get_char(self) -> chr:
"""
Return '@' to represent that this Raccoon is inside a garbage can
or 'R' otherwise.
"""
if self.inside_can:
return '@'
return 'R'
class SmartRaccoon(Raccoon):
"""A smart raccoon in the game.
Behaves like a Raccoon, but when it takes a turn, it will move towards
a GarbageCan if it can see that GarbageCan in its line of sight.
See the take_turn method for details.
SmartRaccoons move in the same way as Raccoons.
=== Sample Usage ===
>>> b = GameBoard(8, 1)
>>> s = SmartRaccoon(b, 4, 0)
>>> s.x, s.y
(4, 0)
>>> s.inside_can
False
"""
def take_turn(self) -> None:
"""Take a turn in the game.
If a SmartRaccoon is in a GarbageCan, it stays where it is.
A SmartRaccoon checks along the four directions for
the closest non-occupied GarbageCan that has nothing blocking
it from reaching that GarbageCan (except possibly the Player).
If there is a tie for the closest GarbageCan, a SmartRaccoon
will prioritize the directions in the order indicated in DIRECTIONS.
If there are no GarbageCans in its line of sight along one of the four
directions, it moves exactly like a Raccoon. A GarbageCan is in its
line of sight if there are no other Raccoons, RecyclingBins, or other
GarbageCans between this SmartRaccoon and the GarbageCan. The Player
may be between this SmartRaccoon and the GarbageCan though.
>>> b = GameBoard(8, 2)
>>> s = SmartRaccoon(b, 4, 0)
>>> _ = GarbageCan(b, 3, 1, False)
>>> _ = GarbageCan(b, 0, 0, False)
>>> _ = GarbageCan(b, 7, 0, False)
>>> s.take_turn()
>>> s.x == 5
True
>>> s.take_turn()
>>> s.x == 6
True
"""
# TODO Task #4
def get_char(self) -> chr:
"""
Return '@' to represent that this SmartRaccoon is inside a Garbage Can
and 'S' otherwise.
"""
if self.inside_can:
return '@'
return 'S'
class GarbageCan(Character):
"""A garbage can in the game.
=== Public Attributes ===
locked:
whether or not this GarbageCan is locked.
=== Sample Usage ===
>>> b = GameBoard(2, 2)
>>> g = GarbageCan(b, 0, 0, False)
>>> g.x, g.y
(0, 0)
>>> g.locked
False
"""
locked: bool
def __init__(self, b: GameBoard, x: int, y: int, locked: bool) -> None:
"""Initialize this GarbageCan to be at tile (<x>, <y>) and store
whether it is locked or not based on <locked>.
"""
Character.__init__(self, b, x, y)
self.locked = locked
def get_char(self) -> chr:
"""
Return 'C' to represent a closed garbage can and 'O' to represent
an open garbage can.
"""
if self.locked:
return 'C'
return 'O'
def move(self, direction: Tuple[int, int]) -> bool:
"""
Garbage cans cannot move, so always return False.
"""
return False
# A helper function you may find useful for Task #5, depending on how
# you implement it.
def get_neighbours(tile: Tuple[int, int]) -> List[Tuple[int, int]]:
"""
Return the coordinates of the four tiles adjacent to <tile>.
This does NOT check if they are valid coordinates of a board.
>>> ns = set(get_neighbours((2, 3)))
>>> {(2, 2), (2, 4), (1, 3), (3, 3)} == ns
True
"""
rslt = []
for direction in DIRECTIONS:
rslt.append((tile[0] + direction[0], tile[1] + direction[1]))
return rslt
if __name__ == '__main__':
import doctest
doctest.testmod()
import python_ta
python_ta.check_all(config={
'allowed-io': [],
'allowed-import-modules': ['doctest', 'python_ta', 'typing',
'random', '__future__', 'math'],
'disable': ['E1136'],
'max-attributes': 15,
'max-module-lines': 1600
})