Page 1 of 1

Storing a Bool() expression as a structure field?

Posted: Sat Sep 28, 2024 8:49 pm
by Quin
I already posted this question to the SpiderBasic forums, but this forum tends to get more traffic and I imagine the solution would work across both.
I have a structure like so:

Code: Select all

Structure Quest
  Name.s
  Unlocked.b
  UnlockRequirements.?
EndStructure
I want the UnlockRequirements field to store a conditional expression like you'd pass to Bool(), so I can create quests like: Quests\UnlockRequirements = (StepsTaken >= 1000 And TimePlayed >= 60000)
Is this possible?
Thanks!

Re: Storing a Bool() expression as a structure field?

Posted: Sat Sep 28, 2024 9:26 pm
by Psychophanta
Of course yes, it is possible. The problem, at least from my perspective, is that you lost 7 bits of memory if you name as 'byte' type the bool variable:

Code: Select all

Structure Quest
  Name.s
  Unlocked.b
  UnlockRequirements.b
EndStructure

var.Quest\UnlockRequirements = Bool(StepsTaken >= 1000 And TimePlayed >= 60000)
So, to thake advantage of the other 7 bits of Quests\UnlockRequirements you should manage this variable as 8 bool variables in itself.

Re: Storing a Bool() expression as a structure field?

Posted: Sat Sep 28, 2024 10:59 pm
by Quin
I don't think this will do what I want, I want to query this expression at runtime periodically to see if the user completed the quest. If I'm not mistaken, assigning the result of Bool() to the structure field won't allow for this.

Re: Storing a Bool() expression as a structure field?

Posted: Sat Sep 28, 2024 11:07 pm
by idle
use binary enums perhaps , then if you have specific requirements you can test to see if those are met singly or in combination

Code: Select all

EnumerationBinary 
  #one
  #two
  #three
  #four
EndEnumeration 

Structure Quest 
  name.s 
  state.i 
EndStructure 

x.quest 
x\name = "one" 
x\state = #one 

x\name = "two"
x\state | #two 

Debug Bool(x\state & #one) 
Debug Bool(x\state & #two ) 

Debug Bool(x\state & #two | #one) 
Debug Bool(x\state & #three)  


Re: Storing a Bool() expression as a structure field?

Posted: Sun Sep 29, 2024 9:45 am
by breeze4me
If you only have a few conditional expressions, you could probably use a procedure pointer.
However, if you need to use multiple complex conditions, it's probably better to specify the condition as a string and parse it.

Example 1.

Code: Select all

Structure Quest
  Name.s
  Unlocked.b
  *UnlockRequirements
EndStructure


Procedure Bool_A_ge_B_And_C_ge_D(a, b, c, d)
  ProcedureReturn Bool(a >= b And c >= d)
EndProcedure

Procedure Bool_A_ge_B(a, b)
  ProcedureReturn Bool(a >= b)
EndProcedure


a.Quest\UnlockRequirements = @Bool_A_ge_B_And_C_ge_D()
b.Quest\UnlockRequirements = @Bool_A_ge_B()


StepsTaken = 1001
TimePlayed = 60001
logsSold = 101

; StepsTaken >= 1000 And TimePlayed >= 60000
Debug CallFunctionFast(a\UnlockRequirements, StepsTaken, 1000, TimePlayed, 60000)

; logsSold >= 100
Debug CallFunctionFast(b\UnlockRequirements, logsSold, 100)
Example 2.

Code: Select all

Prototype Bool_A_ge_B_And_C_ge_D(a, b, c, d)
Prototype Bool_A_ge_B(a, b)

Structure Quest
  Name.s
  Unlocked.b
  StructureUnion
    UnlockRequirements_A_ge_B_And_C_ge_D.Bool_A_ge_B_And_C_ge_D
    UnlockRequirements_A_ge_B.Bool_A_ge_B
  EndStructureUnion
EndStructure


Procedure Bool_A_ge_B_And_C_ge_D(a, b, c, d)
  ProcedureReturn Bool(a >= b And c >= d)
EndProcedure

Procedure Bool_A_ge_B(a, b)
  ProcedureReturn Bool(a >= b)
EndProcedure


a.Quest\UnlockRequirements_A_ge_B_And_C_ge_D = @Bool_A_ge_B_And_C_ge_D()
b.Quest\UnlockRequirements_A_ge_B = @Bool_A_ge_B()


StepsTaken = 1001
TimePlayed = 60001
logsSold = 101

; StepsTaken >= 1000 And TimePlayed >= 60000
Debug a\UnlockRequirements_A_ge_B_And_C_ge_D(StepsTaken, 1000, TimePlayed, 60000)

; logsSold >= 100
Debug b\UnlockRequirements_A_ge_B(logsSold, 100)
Edit:
If your conditional expression has a specific pattern, you can parse it as shown below.
(use of runtime global variables.)
The code below is just a rough example, and will need more error checking for completeness.

Code: Select all

Structure Quest
  Name.s
  Unlocked.b
  UnlockRequirements.s
EndStructure

Global StepsTaken
Runtime StepsTaken

Global TimePlayed
Runtime TimePlayed

Global logsSold
Runtime logsSold


Procedure BoolStr(exp.s)
  Protected result, count, i, ii
  Protected part.s
  Protected Dim v.i(0)
  Protected tmp = 1
  
  exp = Trim(exp)
  If exp = "" : ProcedureReturn 0 : EndIf
  
  count = CountString(exp, "&") + 1
  
  ReDim v.i(count * 2 - 1)
  
  For i = 1 To count
    part = Trim(StringField(exp, i, "&"))
    If part
      v(ii) = GetRuntimeInteger(StringField(part, 1, " "))
      v(ii + 1) = Val(StringField(part, 2, " "))
      
      ii + 2
    EndIf
  Next
  
  i = 0
  
  While ii > 0
    
    tmp = Bool( tmp And Bool(v(i) >= v(i+1)) )
    
    ;Debug "v(i) : " + v(i)
    ;Debug "v(i + 1) : " + v(i + 1)
    
    i + 2
    ii - 2
    result = tmp
    
    ;Debug "tmp : " + result
  Wend
  
  ;Debug "final result : " + result
  
  ProcedureReturn result
EndProcedure



a.Quest\UnlockRequirements = "StepsTaken 1000 & TimePlayed 60000"
b.Quest\UnlockRequirements = "logsSold 100"

StepsTaken = 1001
TimePlayed = 60001
logsSold = 101

; StepsTaken >= 1000 And TimePlayed >= 60000
Debug BoolStr(a\UnlockRequirements)

; logsSold >= 100
Debug BoolStr(b\UnlockRequirements)
Edit2:
If you have a fixed pattern of conditions, you can also use an interface instead of a string, as shown below.
As long as you're careful with the scopes of the variables, you can avoid using global variables.

Edit3:
Bug fixed, and shorten code.
By the way, I'm not sure there's any advantage to this approach other than convenience. If your condition expressions are simple, directly inserting it (or a function with a comparison expression) can result in shorter code.

Code: Select all

Structure Quest
  Name.s
  Unlocked.b
  *UnlockRequirementsVar.Integer[2]
  UnlockRequirementsValue.i[2]
EndStructure

StepsTaken = 1000
TimePlayed = 60000
logsSold = 100

Define a.Quest
Define b.Quest

; StepsTaken >= 1000 And TimePlayed >= 60000

a\UnlockRequirementsVar[0] = @StepsTaken
a\UnlockRequirementsValue[0] = 1000

a\UnlockRequirementsVar[1] = @TimePlayed
a\UnlockRequirementsValue[1] = 60000

If a\UnlockRequirementsVar[0]\i >= a\UnlockRequirementsValue[0] And a\UnlockRequirementsVar[1]\i >= a\UnlockRequirementsValue[1]
  a\Unlocked = 1
  Debug "a unlocked"
EndIf

; logsSold >= 100

b\UnlockRequirementsVar[0] = @logsSold
b\UnlockRequirementsValue[0] = 100

If b\UnlockRequirementsVar[0]\i >= b\UnlockRequirementsValue[0]
  b\Unlocked = 1
  Debug "b unlocked"
EndIf

Code: Select all

Interface IUnlockRequirements
  GetUnlockResult()
  SetExpressionVal(*var1.Integer, val1, *var2.Integer = 0, val2 = 0, *var3.Integer = 0, val3 = 0, *var4.Integer = 0, val4 = 0)
EndInterface

Structure IUnlockRequirementsVtbl
  *vtble
  *GetUnlockResult
  *SetExpressionVal
  
  *var.Integer[4]
  val.i[4]
EndStructure

Procedure GetUnlockResult(*this.IUnlockRequirementsVtbl)
  Protected Result, i, tmp = 1
  If *this
    For i = 0 To 3
      If *this\var[i]
        tmp = Bool( tmp And Bool( *this\var[i]\i >= *this\val[i]) )
        Result = tmp
      EndIf
    Next
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure SetExpressionVal(*this.IUnlockRequirementsVtbl, *var1.Integer, val1, *var2.Integer = 0, val2 = 0, *var3.Integer = 0, val3 = 0, *var4.Integer = 0, val4 = 0)
  If *this
    With *this
      \var[0] = *var1
      \val[0] = val1
      
      \var[1] = *var2
      \val[1] = val2
      
      \var[2] = *var3
      \val[2] = val3
      
      \var[3] = *var4
      \val[3] = val4
    EndWith
    
    If *var1 = 0 : ProcedureReturn 0 : EndIf
    If *var2 = 0 : ProcedureReturn 1 : EndIf
    If *var3 = 0 : ProcedureReturn 2 : EndIf
    If *var4 = 0 : ProcedureReturn 3 : EndIf
    ProcedureReturn 4
  EndIf
  ProcedureReturn 0
EndProcedure

Procedure CreateUnlockRequirementObj(*var1.Integer, val1, *var2.Integer = 0, val2 = 0, *var3.Integer = 0, val3 = 0, *var4.Integer = 0, val4 = 0)
  Protected Result, *m.IUnlockRequirementsVtbl = AllocateMemory(SizeOf(IUnlockRequirementsVtbl))
  If *m
    *m\vtble = *m + OffsetOf(IUnlockRequirementsVtbl\GetUnlockResult)
    *m\GetUnlockResult = @GetUnlockResult()
    *m\SetExpressionVal = @SetExpressionVal()
    
    Result = SetExpressionVal(*m, *var1, val1, *var2, val2, *var3, val3, *var4, val4)
    If Result > 0
      ProcedureReturn *m
    EndIf
    FreeMemory(*m)
  EndIf
  ProcedureReturn 0
EndProcedure


Structure Quest
  Name.s
  Unlocked.b
  *UnlockRequirements.IUnlockRequirements
EndStructure

Global StepsTaken, TimePlayed, logsSold

a.Quest\UnlockRequirements = CreateUnlockRequirementObj(@StepsTaken, 1000, @TimePlayed, 60000)
If a\UnlockRequirements = 0
  Debug "Obj A creation failed."
EndIf

b.Quest\UnlockRequirements = CreateUnlockRequirementObj(@logsSold, 100)
If b\UnlockRequirements = 0
  Debug "Obj B creation failed."
EndIf

StepsTaken = 1000
TimePlayed = 60000
logsSold = 100

; StepsTaken >= 1000 And TimePlayed >= 60000
If a\UnlockRequirements
  Debug a\UnlockRequirements\GetUnlockResult()
EndIf

; logsSold >= 100
If b\UnlockRequirements
  Debug b\UnlockRequirements\GetUnlockResult()
EndIf

Debug "Values changed"

StepsTaken = 999
TimePlayed = 60000
logsSold = 99

; StepsTaken >= 1000 And TimePlayed >= 60000
If a\UnlockRequirements
  Debug a\UnlockRequirements\GetUnlockResult()
EndIf

; logsSold >= 100
If b\UnlockRequirements
  Debug b\UnlockRequirements\GetUnlockResult()
EndIf

Debug "Set new expressions"

Global StepsTaken2, TimePlayed2, logsSold2

StepsTaken2 = 100 ;- 1
TimePlayed2 = 6000 ;- 1
logsSold2 = 90 ;- 1

If a\UnlockRequirements
  ; StepsTaken2 >= 100 And TimePlayed2 >= 6000
  a\UnlockRequirements\SetExpressionVal(@StepsTaken2, 100, @TimePlayed2, 6000)
  Debug a\UnlockRequirements\GetUnlockResult()
  
  
  StepsTaken2 = 100
  
  ; StepsTaken2 >= 1000
  a\UnlockRequirements\SetExpressionVal(@StepsTaken2, 1000)
  Debug a\UnlockRequirements\GetUnlockResult()
EndIf

If b\UnlockRequirements
  ; logsSold2 >= 90
  b\UnlockRequirements\SetExpressionVal(@logsSold2, 90)
  Debug b\UnlockRequirements\GetUnlockResult()
EndIf