Reputation: 13
I'm making a little text adventure in Smalltalk. It's made up of "screens" that have their texts and choices for other screens included. Since I want the game to be dynamic, I also want to include branching. For instance, if the player is at a blacksmith and wants to buy an axe, the screen the player goes to immediately checks if the player has enough money and jumps to one of two other screens based on that.
I already have this working: The screens (classes named Place
) have a list where the first item is the function and the following items are the arguments. However, I have it done in a very ugly way: the first item is a string that is then compared against in a big "action" method, so it looks something like this:
game data method:
blacksmith := Place new.
blacksmith choiceText: 'I would like an axe.';
blacksmith action add: 'money'; add: 20; add: blacksmith_good; add: blacksmith_bad.
action method: (currentScreen is also a Place
; the class also contains a BranchMoney
method that does the actual decision making)
(currentScreen action at: 1) = 'money'
ifTrue: [
currentScreen := (currentScreen BranchMoney)
]
That's obviously not ideal, and I would like to compact it by doing something like this:
game data method:
blacksmith action add: [blacksmith BranchMoney]; add: 20; add: blacksmith_good; add: blacksmith_bad.
action method:
currentScreen := (currentScreen action at: 1)
So that instead of string checking the game would just directly proceed with the method I want it to do.
However, it doesn't seem to work - I've tried different changes to the code, and the problem seems to be that the currentScreen := (currentScreen action at: 1)
line just replaces the contents of currentScreen with the code block contents – it doesn't calculate the block's contents and use its resulting value that is of type Place
.
I've tried using round brackets in the game data method – that throws a list out of bounds exception, because it tries to calculate the expression immediately, before other arguments have even been added. Changing the first item name in game data method to currentScreen BranchMoney
doesn't seem to make a difference.
I've also tried adding a return in the game data method, like this: blacksmith action add: [^blacksmith BranchMoney]
, so that it would have a value to return, no luck. Doing something like currentScreen := [^currentScreen action at: 1]
in the action method doesn't work either.
For some shots in the dark, I tried the ExternalProcedure
call
and call:
methods, but that failed too.
Upvotes: 1
Views: 452
Reputation: 14868
In Smalltalk every block is a regular object that you can store and retrieve the same you would do with any other object:
b := [self doSomething]
stores in b
the block (much as b := 'Hello'
stores a string in b
). What I think you are missing is the #value
message. To execute the block do the following
b value "execute self doSomething and answer with the result"
In case your block has one argument use #value:
instead
b := [:arg | self doSomethingWith: arg]
later on
b value: 17 "execute the block passing 17 as the argument"
(for two arguments use #value:value:
, for three #value:value:value:
and for many #valueWithArguments:
.)
Note however that this approach of using blocks and Arrays of arguments doesn't look very elegant (or even convenient). However, to help you with some better alternative we would need to learn more about your game. So, go check whether #value
(and friends) let you progress a little bit and feel free to come back here with your next question. After some few iterations we could guide you towards a clearer route.
Example
b := [:m | m < 20 ifTrue: ['bad'] ifFalse: ['good']].
will produce
b value: 15 "==> 'bad'"
b value: 25 "==> 'good'"
Upvotes: 3