⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{collection|RCRPG}} This [[Oz]] version of [[:Category:RCRPG|RCRPG]] implements a text interface.
It is a purely functional implementation, i.e. the state of the world is passed as an argument to functions. If a function modifies the world, a new version is returned.
It implements the same commands as the other implementations (no "alias", "name" or "help" commands, though).
==Code==
declare
%% Basic data structures
fun {CreateWorld Goal}
world(goal:Goal
position:[0 0 0]
equipped:empty
inventory:nil
status:nil
%% initially we only have one room
%% coords: X Y Z
rooms:unit(0:unit(0:unit(0:{CreateRoom sledge})))
)
end
fun {CreateRoom InitialItem}
room(items:[InitialItem]
north:wall
south:wall
east:wall
west:wall
up:wall
down:wall)
end
%% COMMANDS (and shortcuts)
fun {CreateCommandTable}
Commands =
unit(north: {Curry1of2 Go north} n:Commands.north
south: {Curry1of2 Go south} s:Commands.south
east: {Curry1of2 Go east} e:Commands.east
west: {Curry1of2 Go west} w:Commands.west
up: {Curry1of2 Go up} u:Commands.up
down: {Curry1of2 Go down} d:Commands.down
attack:Attack a:Commands.attack
drop: Drop
take: Take
inventory:Inventory i:Commands.inventory
inv: Commands.inventory
equip: Equip
)
in
Commands
end
CommandTable = {Value.byNeed CreateCommandTable}
%% The game loop.
proc {Loop World0}
{PrintStatus World0}
World = {SetStatus World0 nil}
in
if World.position == World.goal then
{System.showInfo "You are now in the goal room. You have won the game!"}
else
{System.printInfo ">"}
%% Read and parse user input
Tokens = {Map {String.tokens {ReadLine} & } String.toAtom}
in
if Tokens == nil then %% repeat room description when user just presses enter
{Loop World}
else
ComName|Args = Tokens
in
if {Not {HasFeature CommandTable ComName}} then
{Loop {SetStatus World "Unknown command."}}
else
ComFunction = CommandTable.ComName
in
%% check for number of args (+2: "World" in and out)
if {Procedure.arity ComFunction} \= 2 + {Length Args} then
{Loop {SetStatus World "Wrong number of arguments."}}
else
WorldAfterCommand
in
%% dynamically call ComFunction
%% (statically unknown number of arguments)
{Procedure.apply ComFunction World|{Append Args [WorldAfterCommand]}}
{Loop WorldAfterCommand}
end
end
end
end
end
%% IMPLEMENTATION OF COMMANDS
%% All commands take the "world" as their first argument.
%% They might take additional arguments (from the user).
%% All commands return the new world.
%% Try to go one step into the specified direction.
%% Direction: atom like 'north'
%% (Go is not a real command; it only becomes one after currying.)
fun {Go Direction World}
CR = {GetCurrentRoom World}
in
if CR.Direction == open then
if Direction == up andthen {Not {Member ladder CR.items}} then
{SetStatus World "There is no ladder in this room."}
else
{AdjoinAt World position {MovePos World.position Direction}}
end
else
{SetStatus World "There is a wall in that direction."}
end
end
fun {Attack World Direction}
if {Not {IsDirection Direction}} then
{SetStatus World "Unknown direction"}
elseif World.equipped == sledge then
case {GetCurrentRoom World}.Direction
of wall then
{SetStatus {ConnectCurrentRoomTo World Direction}
"I made a hole in the wall."}
[] open then
{SetStatus World "There is already a connection in that direction."}
end
else
{SetStatus World "Can't attack with this item."}
end
end
%% Item: name of item as atom
fun {Drop World Item}
if Item == all then {FoldL World.inventory Drop World}
else
if {Member Item World.inventory} then
CR = {GetCurrentRoom World}
%% add to room
NewRoom = {AdjoinAt CR items Item|CR.items}
World2 = {SetRoom World World.position NewRoom}
%% remove from inventory
RemainingItems = {Remove World2.inventory Item}
World3 = {AdjoinAt World3 inventory RemainingItems}
%% possibly update "equipped"
World4 = if {Not {Member World3.equipped RemainingItems}} then
{AdjoinAt World3 equipped empty}
else
World3
end
in
{Inventory World4}
else
{SetStatus World "Not carrying such an item."}
end
end
end
%% Item: item name as an atom
fun {Take World Item}
CR = {GetCurrentRoom World}
in
if Item == all then {FoldL CR.items Take World}
else
if {Member Item CR.items} then
%% remove from room
NewRoom = {AdjoinAt CR items {Remove CR.items Item}}
World2 = {SetRoom World World.position NewRoom}
%% add to inventory
World3 = {AdjoinAt World2 inventory Item|World2.inventory}
in
{Inventory World3}
else
{SetStatus World "There is no such item in this room."}
end
end
end
fun {Equip World Item}
if {Member Item World.inventory} then
{SetStatus {AdjoinAt World equipped Item}
"Equipped with "#Item}
else
{SetStatus World "No such item in inventory."}
end
end
%% Shows the inventory as the status.
fun {Inventory World}
{SetStatus World "inventory: "#{ListToString World.inventory}}
end
%% Operations on basic data structures (the "world" and rooms)
proc {PrintStatus World}
if World.status == nil then %% describe room if no status
[X Y Z] = World.position
in
{System.showInfo
"You are in room ("#X#", "#Y#", "#Z#").\n"#
"The following items are in this room: "#
{ListToString {GetCurrentRoom World}.items}#"."}
else
{System.showInfo World.status}
end
end
fun {SetStatus World Status}
{AdjoinAt World status Status}
end
fun {NewRoom}
{CreateRoom {RandomlySelect [sledge gold ladder]}}
end
%% Returns the room at the given position.
%% Might modify the world (if the room does not yet exist) and returns the new world
%% in 'NewWorld'.
fun {GetRoom World Position ?NewWorld}
{CondGet World rooms|Position NewRoom ?NewWorld}
end
fun {GetCurrentRoom World}
[X Y Z] = World.position
in
World.rooms.X.Y.Z
end
%% Replaces a room. Returns the new world.
fun {SetRoom World Pos NewRoom}
{Set World rooms|Pos NewRoom}
end
local
%% Maps directions to movement deltas.
DirPos = unit(east: [~1 0 0] west: [1 0 0]
north:[0 ~1 0] south:[0 1 0]
up: [0 0 ~1] down: [0 0 1])
in
%% Returns the position that results from going one step
%% into the specified direction.
fun {MovePos Pos Dir}
{List.zip Pos DirPos.Dir Number.'+'}
end
end
local
OppositeDir = unit(east:west west:east
north:south south:north
up:down down:up)
in
fun {IsDirection X} {HasFeature OppositeDir X} end
%% Connects the current room to a neighbouring room.
%% Returns the new world.
fun {ConnectCurrentRoomTo World Direction}
World2 = {OpenRoom World World.position Direction}
OtherPos = {MovePos World2.position Direction}
in
{OpenRoom World2 OtherPos OppositeDir.Direction}
end
end
fun {OpenRoom World RoomPosition Direction}
World2
Room = {GetRoom World RoomPosition ?World2}
in
{SetRoom World2 RoomPosition {AdjoinAt Room Direction open}}
end
%% general helpers
%% Creates a one-argument function from a two-argument function.
fun {Curry1of2 Fun Arg1}
fun {$ Arg2}
Return
in
{Procedure.apply Fun [Arg1 Arg2 Return]}
Return
end
end
%% Removes the first occurance of Y from the list Xs.
fun {Remove Xs Y}
case Xs of !Y|Yr then Yr
[] X|Xr then X|{Remove Xr Y}
[] nil then nil
end
end
%% Returns a randomly picked element of Xs.
fun {RandomlySelect Xs}
Idx = {OS.rand} * {Length Xs} div {OS.randLimits _} + 1
in
{Nth Xs Idx}
end
%% Reads a line from stdin.
local
StdIn = {New class $ from Open.file Open.text end init(name:stdin)}
in
fun {ReadLine}
{StdIn getS($)}
end
end
fun {ListToString Xs}
{Value.toVirtualString Xs 1000 1000}
end
%% Support for using nested records as multidimensional immutable arrays
Nothing = {NewName}
%% Returns a value from an array where Fs is a list of array indices.
%% If the specified entry does not exist, it is created by calling Otherwise
%% and the new array is returned in NewArr.
%% Example: Val = {CondGet a(1:b(3:42)) [1 3] _ _} == 42
fun {CondGet Arr Fs Otherwise ?NewArr}
case Fs of F|Fr then
Arr2 = if Arr == Nothing then unit else Arr end
NewArrF
Res = {CondGet {CondSelect Arr2 F Nothing} Fr Otherwise ?NewArrF}
in
NewArr = {AdjoinAt Arr2 F NewArrF}
Res
[] nil then
NewArr = if Arr == Nothing then {Otherwise} else Arr end
NewArr
end
end
%% Sets a (new or existing) entry in Arr to V.
%% Returns the new array.
fun {Set Arr Fs V}
case Fs of F|Fr then
Arr2 = if Arr == Nothing then unit else Arr end
in
{AdjoinAt Arr2 F {Set {CondSelect Arr2 F Nothing} Fr V}}
[] nil then
V
end
end
in
%% Start game
{Loop {CreateWorld [1 1 5]}}