Göm menyn

Laboration 5 - Datastrukturer och Interpretatorer

Syftet med denna laboration är att ni ska lära er att skapa modeller av ett scenario för att sedan kunna modifiera och utveckla detta scenario enligt en uppsättning regler som inte får förändras allt eftersom scenariot förändras. Ni kommer dessutom lära er att interpretera och tolka data för att sedan översätta det till ett format som bättre passar ert program.

Inlämningsuppgifter

Sokoban är ett datorspel från Japan där ni kontrollerar en lagerarbetare som ska försöka knuffa alla lådor till sina rätta platser i ett varuhus. Reglerna är enkla och det finns inte särskilt många olika typer av objekt att hålla reda på. Lagerarbetaren kan röra sig upp, ner, till vänster och till höger. Han kan knuffa lådor som är framför honom så länge som det inte är en vägg eller en annan låda bakom den låda han knuffar. Spelet slutar först då alla lådor är på rätt plats.

OBS:Under denna laboration kommer du implementera och tillslut redovisa ett fungerande spel. Laborationen är dock indelad i mindre delar för att göra utvecklingen av spelet enklare. Precis som tidigare laborationer så finns det krav på er lösning utöver att det fungerar enligt beskrivningen nedan. Titta därför gärna under rubriken Krav på inlämningen innan ni bestämmer er för hur ett problem skall lösas. Eftersom denna laboration är omfattande så är det av yttersta vikt att ni löpande diskuterar er lösning med assistenterna i kursen. Ett designval i början av laborationen kan komma att kosta mycket tid och svårigheter, i värsta fall kan ett dåligt val leda till att delar eller hela laborationen behöver göras om från början.

Uppgift 5a - Representation av spelplanen

När det gäller att representera en samling objekt eller ett scenario finns det två olika tillvägagångssätt. Det första sättet är att se det hela som ett scenario, i detta fall skulle det betyda att spelplanen är det viktiga och alla händelser i programmet förändrar en detalj på spelplanen. Det andra sättet är att se varje enskilt objekt på spelplanen som en fristående del och det är först när ni använder er av alla delarna tillsammans som en spelplan uppstår. Hur ni väljer att arbeta är givetvis upp till er och det finns givetvis fler varianter där ni tar lite från båda metoderna.

I denna uppgift ska ni skriva funktioner för att bygga upp och förändra en spelplan. Det ska finnas möjlighet att definiera utseendet på spelplanen. Det ska gå att placera ut lådor och lagerplatser på spelplanen. Det ska gå att förflytta en lagerarbetare och knuffa lådor. Utöver detta ska ni dessutom skriva en funktion sokoban_display som ritar ut spelplanen på ett snyggt sätt. Denna utritning ska ligga utanför ert adt och använda sig av selektorerna för att klara av sin uppgift.

Följande symboler ska kunna finnas på spelplanen (observera att de inte direkt behöver representeras enligt nedan i ert program, vissa information kanske inte ens behöver lagras utan kan beräknas vid utritning):

@ Lagerarbetare
0
Låda
# Vägg
.
Lagringsplats
Golvyta (mellanslag)
* Låda på lagringsplats
+ Lagerarbetar på lagringsplats

Körexempel OBS! Det ingår i uppgiften att hitta en lämplig datastruktur, den som visas nedan är endast ett exempel):


>>> board = create_board()
>>> add_wall(board, x = 1, y = 0))
>>> add_wall(board, x = 0, y = 1))
>>> add_wall(board, x = 2, y = 1))
>>> add_wall(board, x = 1, y = 2))
>>> create_player(board, 1, 1)
>>> board
[['wall', 1, 0], ['wall', 0, 1], ['wall', 2, 1], ['wall', 1, 2], ['player',
1, 1]]
>>> soboban_display(board)
 #
#@#
 #
  

Ett annat körexempel OBS! Det ingår i uppgiften att hitta en lämplig datastruktur, den som visas nedan är endast ett exempel):


>>> soko_walls.append(create_wall(x = 1, y = 0))
>>> soko_walls.append(create_wall(x = 0, y = 1))
>>> soko_walls.append(create_wall(x = 2, y = 1))
>>> soko_walls.append(create_wall(x = 1, y = 2))
>>> soko_player = move_player(create_player(), 1, 1)
>>> soko_walls
[['wall', 1, 0], ['wall', 0, 1], ['wall', 2, 1], ['wall', 1, 2]]
>>> soboban_display(wall = soko_walls, player = soko_player)
 #
#@#
 #
  

Skapa alla funktioner ni behöver för att representera spelplanen först. Fundera över vilken funktionalitet som behövs för att hantera spelplanen. Här är några exempel på vad ni kommer behöva göra med spelplanen, lägg till de funktioner ni kommer fram till utöver dessa:

  • Rita ut spelplanen
  • Spara lådor, väggar, lagringsplatser och spelaren
  • Kontrollera vad som finns på en kordinat på spelbrädet
  • Flytta på lådor och spelare
Uppgift 5b - Ladda spelplan från fil
Sokoban är ett populärt spel och det finns många officiella nivåer. Några av dessa nivåer hittar ni i körexemplet nedan. Er uppgift är att skriva funktioner för att ladda in en spelplan från fil så att ni kan använda den tillsammans med de funktioner ni tidigare skrivit för att representera Sokoban. Ni kan hitta de nivåer som är beskrivna nedan på kurskontot för kursen genom denna länk. Där finns dels några nivåer som har filnamn som slutar på .sokoban men även en samling nivåer i filen sokoban_levels.txt. Ur filen med flera banor kan ni kopiera banor till enskilda filer, alternativt kan ni göra egna. Tänk på att ni redan skapat alla funktioner ni behöver för att hantera spelplanen i den tidigare delen av laborationen, använd dessa.

Körexempel:

 
>>> first_level = sokoban_load('first_level.sokoban')
>>> sokoban_display(first_level)
    #####
    #   #
    #o  #
  ###  o##
  #  o o #
### # ## #   ######
#   # ## #####  ..#
# o  o          ..#
##### ### #@##  ..#
    #     #########
    #######
>>> second_level = sokoban_load('second_level.sokoban')
>>> sokoban_display(second_level)
############
#..  #     ###
#..  # o  o  #
#..  #o####  #
#..    @ ##  #
#..  # #  o ##
###### ##o o #
  # o  o o o #
  #    #     #
  ############
Uppgift 5c - Kollisioner

Innan ni kan sätta ihop de olika delarna till ett fullt fungerande spel måste ni se till att lagerarbetaren kan förflytta sig i rummet men inte kunna gå igenom väggar eller lådor. Beroende på er tidigare implementation av spelplanen finns det lite olika möjligheter till kontroller men det är väldigt viktigt att ni tänker igenom alla möjligheter så att det fungerar bra i alla olika riktningar.

Er uppgift är att skriva en funktion player_can_move och en funktion crate_can_move som returnerar sant ifall det är möjligt att flytta en spelare eller en låda till en ny position och falskt ifall det skulle innebära en kollision. Dessa funktioner är obligatoriska och skall fungera enligt denna beskrivning och användas av ert program.

Uppgift 5d - Sokoban

Nu är det dags att sätta ihop de funktioner ni skrivit till ett spel! Ni ska implementera ett användargränssnitt så att en spelare kan navigera lagerarbetaren genom spelplanen och knuffa lådor till sina rätta platser. När spelaren har löst nivån ska spelet avslutas. De olika nivåerna ska laddas från en katalog 'levels' och programmet ska söka igenom katalogen efter nya nivåer vid varje körning.

Körexempel:

 
$ python3 sokoban.py
Welcome to Sokoban, please choose a level:
1. first_level
2. second_level
3. my_level
Choose: 3
######
# .O@#
######
Make your move (a)left, (d)right, (w)up, (s)down: l
######
# *@ #
######
Congratulations, You completed level 'my_level'!
Krav
Nedan följer de krav som finns på hur ni löser denna laboration som är utöver det som framgår i själva instruktionen:
  • Precis som föregående laboration finns det i denna laboration strikta krav på er ADT och att er representation av data är vettig.
  • Du får inte spara mellanslag i någon datastruktur som används för att representera spelplanen. Det vi försöker säga är alltså att golvytor på spelplanen inte får sparas på något sätt i spelplanen, det går alltså inte att spara None istället för ett mellanslag i spelplanen. Ni får tillfälligt spara mellanslag i er displayfunktion vid behov.
  • De olika delarna av spelet skall vara väl inkapslade och inte ha överlappande funktionalitet. Ett bra mått är exempelvis att ni skulle kunna byta ut er del som hanterar utskrift av kartan utan att behöva ändra i någon annan del.
Exempel på brott mot inkapslingen
Nedan visas ett exempel där displayfunktionen bryter mot inkapslingen. Och ett där den inte gör det. Observera att detta endast är ett exempel på hur en displayfunktion kan se ut. Desutom är den interna representationen av data inte tillåten i denna laboration eftersom den sparar mellanslagen.

# Kartan:
# #@#
# # #

# Intern representation:
# [
#   [(1, "#"), (2, "@"), (3, "#")],
#   [(1, "#"), (2, " "), (3, "#")]
# ]
    
def sokoban_display(board):
    max_x = len(board[-1]) #hanterar board som lista
    max_y = len(board)     #samma som ovan
    for x in range(1, max_x + 1):
        for y in range(1, max_y + 1):
            print(board[y][x][1]) #hanterar board som lista
                                  #och elementen som tuplar
        print()


def sokoban_display(board):
    max_x = get_max_x(board) #använder adt för att hämta max_x
    max_y = get_max_y(board) #samma som ovan
    for x in range(1, max_x + 1):
        for y in range(1, max_y + 1):
            symbol = get_symbol(x, y) #använder adt för att hämta symbolen
            print(symbol, end="")
        print()

  

Sidansvarig: Pontus Haglund
Senast uppdaterad: 2024-10-02