Learn Intermediate Python 3, Specialized Collections, The Great Robot Race

Link to project:
The Great Robot Race
Here are the files:
robot_race.py:

import robot_race_functions as rr
from collections import deque, Counter, namedtuple
from time import time, sleep
 
maze_file_name = 'maze_data_1.csv'
seconds_between_turns = 0.3
max_turns = 35
 
# Initialize the robot race
maze_data = rr.read_maze(maze_file_name)
rr.print_maze(maze_data)
walls, goal, bots = rr.process_maze_init(maze_data)
 
# Populate a deque of all robot commands for the provided maze
robot_moves = deque()
num_of_turns = 0
while not rr.is_race_over(bots) and num_of_turns < max_turns:
  # For every bot in the list of bots, if the bot has not reached the end, add a new move to the robot_moves deque
  # Add your code below!
  for bot in bots:
    if not bot.has_finished:
      robot_moves.append(rr.compute_robot_logic(walls, goal, bot))
 
  num_of_turns += 1
 
# Count the number of moves based on the robot names
# Add your code below!
num_of_moves = Counter(move[0] for move in robot_moves)
 
# Count the number of collisions by robot name
# Add your code below!
num_of_collisions = Counter(move[0] for move in robot_moves if move[2])
 
# Create a namedtuple to keep track of our robots' points
# Add your code below!
BotScoreData = namedtuple("BotScoreData", ["name", "num_moves", "num_collisions", "score"])
 
# Calculate the scores (moves + collisions) for each robot and append it to bot_scores 
bot_scores = []
# Add your code below!
for bot in bots:
  bot_scores.append(BotScoreData(bot.name, num_of_moves, num_of_collisions, num_of_moves + num_of_collisions))
 
# Populate a dict to keep track of the robot movements
bot_data = {}
# Add your code below!
for bot in bots:
  bot_data.update({bot.name: bot})
 
# Move the robots and update the map based on the moves deque
while len(robot_moves) > 0:
  # Make sure to pop moves from the front of the deque
  # Add your code below!
  bot_name, direction, has_collided = robot_moves.popleft()
  bot_data[bot_name].process_move(direction)
 
  # Update the maze characters based on the robot positions and print it to the console
  rr.update_maze_characters(maze_data, bots)
  rr.print_maze(maze_data)
  sleep(seconds_between_turns - time() % seconds_between_turns)
 
# Print out the results!
rr.print_results(bot_scores)

robot_race_functions.py:

import csv
import random as rand
 
 
def read_maze(name):
    maze_chars = []
    with open(name, 'r') as csvfile:
        r = csv.reader(csvfile)
        for row in r:
            maze_chars.append(row)
    return maze_chars
 
 
def print_maze(maze_data):
    for row in maze_data:
        for col in row:
            print(col + ' '),
        print
    print
 
 
def is_race_over(bots):
    done = True
    for bot in bots:
        if not bot.has_finished:
            done = False
    return done
 
 
def print_results(bot_score_data):
    bot_score_data.sort(key=lambda b: b.score)
    place = 1
    print("----- RESULTS -----")
    for score_data in bot_score_data:
        print(str(place) + '. Robot: ' + str(score_data.name))
        print('  ' +  'Score: ' + str(score_data.score) + ' Moves: ' + str(score_data.num_moves) + ' Collisions: ' + str(score_data.num_collisions))
        place += 1
 
 
def process_maze_init(maze_data):
    walls = []
    goal = None
    bots = []
    for r, row in enumerate(maze_data):
        for c, col in enumerate(row):
            if col == '#':
                walls.append(Wall(c,r))
            elif col == '$':
                goal = Goal(c,r)
            elif col.isalpha():
                bots.append(Robot(c,r, col))
    return [walls, goal, bots]
 
 
def compute_robot_logic(walls, goal, bot):
    moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    move_names = {(-1, 0): 'left', (1, 0): 'right', (0, -1): 'down', (0, 1): 'up', (0, 0): 'nothing'}
 
    selected_move = moves[rand.randint(0,3)]
    move_dist = []
    for m, move in enumerate(moves):
        dist = calc_manhattan_dist(bot.calc_x + move[0], bot.calc_y + move[1], goal.x, goal.y)
        move_dist.append([m,dist])
    move_dist.sort(key=lambda x: x[1])
    if rand.random() < 0.45:
        selected_move = moves[move_dist[0][0]]
 
    hit_wall = False
    for wall in walls:
        if bot.calc_x + selected_move[0] == wall.x and bot.calc_y + selected_move[1] == wall.y:
            hit_wall = True
 
    found_alternate_move = False
    if hit_wall:
        for next_move in move_dist:
            move = moves[next_move[0]]
            hit_wall_move = False
            for wall in walls:
                if bot.calc_x + move[0] == wall.x and bot.calc_y + move[1] == wall.y:
                    hit_wall_move = True
 
            if not hit_wall_move:
                selected_move = move
                found_alternate_move = True
                break
 
        if not found_alternate_move:
            selected_move = (0,0)
 
    if bot.calc_x + selected_move[0] == goal.x and bot.calc_y + selected_move[1] == goal.y:
        bot.has_finished = True
        bot.calc_x += selected_move[0]
        bot.calc_y += selected_move[1]
        return bot.name, 'finished', hit_wall
 
    bot.calc_x += selected_move[0]
    bot.calc_y += selected_move[1]
    return bot.name, move_names[selected_move], hit_wall
 
 
def update_maze_characters(old_maze_chars, bots):
    to_replace = []
    for r, row in enumerate(old_maze_chars):
        for c, col in enumerate(row):
            if col.isalpha() or col == '+':
                to_replace.append((c,r))
    for elem in to_replace:
        old_maze_chars[elem[1]][elem[0]] = '_'
    for bot in bots:
        if not bot.remove:
            if old_maze_chars[bot.y][bot.x].isalpha():
                old_maze_chars[bot.y][bot.x] = '+'
            else:
                old_maze_chars[bot.y][bot.x] = bot.name
 
 
def calc_manhattan_dist(x1, y1, x2, y2):
    return abs(x1 - x2) + abs(y1 - y2)
 
 
class Robot:
    def __init__(self, x, y, name):
        self.x = x
        self.calc_x = x
        self.y = y
        self.calc_y = y
        self.has_finished = False
        self.remove = False
        self.name = name
 
    def process_move(self, direction):
        if direction == 'left':
            self.x += -1
        if direction == 'right':
            self.x += 1
        if direction == 'up':
            self.y += 1
        if direction == 'down':
            self.y += -1
        if direction == 'finished':
            self.remove = True
 
 
class Wall:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
 
class Goal:
    def __init__(self, x, y):
        self.x = x
        self.y = y

map_data_1.csv:

#,#,#,#,#,#,#,#,#,#
#,_,_,_,#,_,_,_,$,#
#,_,A,_,_,_,#,_,_,#
#,B,_,_,#,_,#,_,_,#
#,_,C,_,#,_,_,_,_,#
#,#,#,#,#,#,#,#,#,#

map_data_2.csv:

#,#,#,#,#,#,#,#,#,#,#,#,#
#,C,_,_,_,#,_,#,_,_,_,D,#
#,_,_,_,_,_,_,_,_,_,_,_,#
#,_,_,_,#,_,#,_,#,_,_,_,#
#,F,#,_,_,_,$,_,_,_,#,E,#
#,_,_,_,#,_,#,_,#,_,_,_,#
#,_,_,_,_,_,_,_,_,_,_,_,#
#,B,_,_,_,#,_,#,_,_,_,A,#
#,#,#,#,#,#,#,#,#,#,#,#,#

map_data_3.csv:

#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
#,_,_,_,_,_,_,_,#,#,_,_,_,_,_,#,$,#
#,_,#,#,_,_,#,_,#,_,_,#,_,#,_,_,_,#
#,_,_,#,_,_,#,_,_,_,#,_,_,#,_,#,#,#
#,_,_,#,#,#,#,#,#,_,#,_,#,#,_,_,#,#
#,_,_,#,_,_,#,_,_,_,#,_,_,_,_,_,_,#
#,_,_,#,_,_,#,_,_,#,#,#,#,#,#,#,_,#
#,A,B,C,D,E,_,_,_,#,_,_,#,_,_,#,_,#
#,F,G,H,I,J,_,_,_,_,_,_,_,_,_,_,_,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#

robot_race_functions.pyc:

�
d��`c

…(I can’t copy and paste the whole file because Codecademy doesn’t let me)
What is a pyc file, I know what a py file is but not a pyc file?
To me the robot_race_functions.py file is so complicated? I don’t understand parts of the code like this:

I don’t know what abs means. Does it mean absolute value?

What is isalpha()?

Why does the two prints not have brackets like print()?
To let me know, What kind of skill do people have to write code that is similar complexity to the robot_race_functions.py file? Is the robot_race_functions.py file considered a more complex than the average code that a normal skilled programmer writes? Or is it considered basic? Maybe basic?
The robot_race_functions.py file is probably the most complex file I have ever seen in Codecademy? I don’t code that complex files.
:grinning:

The .pyc file is a compiled bytecode version of your code. You do not need to share that, just the content of .py files is fine.

By this point, you really ought to be familiar with the standard library, especially the built-ins…

Again, the documentation is your friend…

… because that project appears to rely on Python 2… :man_facepalming:

1 Like

What is bytecode? It can’t be binary because the file looks like a pile of unfamiliar characters; not numbers.

Why? This course is a Python 3 course. Why does Codecademy hire that person in a Python 3 course? So the robot_race_functions is a Python 2 file? How does this project allow both Python 2 and Python 3? Python 2 is now out of date. It is called dead. Research it if you want. Is isalpha() a fundamental function? If it is than why wasn’t it in the Learn Python 3 Course? People will now not fix Python 2 even if someone finds a security problem in it. It was sunsetted on 1 January 2021; about 8 months ago. It is no longer supported.

It’s bytecode. The docs have a very good glossary, you know…

Yes, I am aware that the project appears in a Python 3 course. I’ve raised this as a bug with Codecademy.

Technically speaking, both the robot_race.py and robot_race_functions.py files will be evaluated by Python 2 because that’s the version invoked when you run python [filename] (e.g. python robot_race.py) in the terminal inside the lesson.

You can see it quite clearly if you just invoke the interpreter:

$ python
Python 2.7.17 (default, Feb 27 2021, 15:10:58) 
[GCC 7.5.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Whilst there are differences, they aren’t likely to cause any issues in this project - you’re leveraging pre-built functions which are provided, and just bringing them together into the final program.

isalpha() is a string method, but whether or not it is considered a “fundamental” function is irrelevant. None of the material on Codecademy is exhaustive, it will not (and likely never will) be designed to teach you everything.

There is an implicit assumption throughout that you will take responsibility for your own learning, and will go beyond the bounds of the learning environment to expand your understanding of both the concepts delivered by the Codecademy material and the language itself.

This is why each of my answers refers back to the documentation. Python’s documentation is really quite good, and should be one of your first ports of call if you see something that you’re not familiar with.

4 Likes

I’m having some serious trouble completing this project.
Does anyone have an idea on how to start?

I can’t tell if the first direction is Checkpoint 4 or Checkpoint 5. It appears to be 5, and if so, how to proceed?
Sorry for such a noob question!

Cheers.

1 Like

Can you show me your code and your Codecademy page so that I can see what task 4 or task 5 is?

Yes can do. I’ve figured out Checkpoint 5 is the starting point.

The instructions are:

The first while loop iterates until the race is over. Inside of this, loop through every bot which has not finished the race yet ( bot.has_finished == False ). Pass the walls , goal , and robot to the compute_robot_logic function in that order. This will return the robot’s decision given its position in the map in the form of a tuple containing (robot_name, action, has_collided) . Append the action to the robot_moves deque .

The code is:

from collections import deque, Counter, namedtuple
from time import time, sleep

maze_file_name = 'maze_data_1.csv'
seconds_between_turns = 0.3
max_turns = 35

# Initialize the robot race
maze_data = rr.read_maze(maze_file_name)
rr.print_maze(maze_data)
walls, goal, bots = rr.process_maze_init(maze_data)

# Populate a deque of all robot commands for the provided maze
robot_moves = deque()
num_of_turns = 0
while not rr.is_race_over(bots) and num_of_turns < max_turns:
  # For every bot in the list of bots, if the bot has not reached the end, add a new move to the robot_moves deque
  # Add your code below!
  robot_moves.append(rr.compute_robot_logic([(3, 0), (1, 5), (2, 3), (2, 4), (3, 3)], (7, 0), [(1, 1), (2, 0), (3, 1)])
  num_of_turns += 1

When I write, robot_moves.append(rr.compute_robot_logic(walls, goals, bots) - as these variables are determined by code above, I get an error indicating ‘bots’ does not have (x, y) variables.
So I manually inputted relevant (x, y) coordinates, however now the last line gives me a SyntaxError which I can’t make sense of:

num_of_turns += 1
           ^
SyntaxError: invalid syntax

I feel like this particular project is broken? But it could of course just be me.
Thanks!

SyntaxError tends to point to the line after the error. I’m unfamiliar with the project, but you’re missing a ) at the end of that line.

2 Likes

Ah. Much appreciated. However after fixing this SyntaxError, I face more issues = seemingly with the code.
Perhaps you could check out the project here: https://www.codecademy.com/courses/learn-intermediate-python-3/projects/the-great-robot-race-python-project
And let me know if it’s functioning correctly?
Thanks again!

I got to that step, doesn’t seem to be broken. For testing purposes, it would have been nice of them to stick a break in the infinite while loop they put at the bottom of the program.

while len(robot_moves) > 0:
  # Make sure to pop moves from the front of the deque
  # Add your code below!

  break  #added break to prevent print spam of maps

  # Update the maze characters based on the robot positions and print it to the console
  rr.update_maze_characters(maze_data, bots)
  rr.print_maze(maze_data)
  sleep(seconds_between_turns - time() % seconds_between_turns)

compute_robot_logic() takes in a single bot, not a list of bots. It asks you to iterate through the bots.

1 Like

Ah I understand. Was able to complete the project with your help - many thanks!

3 Likes