One possible way to fake this result is by aliasing the activity_scores table once for each activity.
select students.id as studentid, students.name as studentname,
s1.score as activity1_score, s2.score as activity2_score,
s3.score as activity3_score, s4.score as activity4_score...
from students, activity_scores as s1, activity_scores as s2,
activity_scores as s3, activity_scores as s4...
where students.id = s1.studentid
and students.id = s2.studentid
and students.id = s3.studentid
and students.id = s4.studentid...
I don't claim that this is in any way efficient, and this assumes that there is a fixed and finite number of activities associated with each and every student. With this version, students that are missing scores in any of the activities don't appear at all in the result set.
To allow for partial fulfillment of activity scores by some students, the joins have to be replaced with left joins.
-- T-SQL
...
where students.id *= s1.studentid
and students.id *= s2.studentid
and students.id *= s3.studentid
and students.id *= s4.studentid...
-- standard SQL
...
from students LEFT JOIN activity_scores as s1 ON students.id = s1.studentid
LEFT JOIN activity_scores as s2 ON students.id = s2.studentid
LEFT JOIN activity_scores as s3 ON students.id = s3.studentid
LEFT JOIN activity_scores as s4 ON students.id = s4.studentid...
I generally prefer to do a straight join (multiple records per student, one per activity score) and then build the resulting single line record using programming at the destination to do the "foreach" style processing that you list above, but I know that is not always possible.