1

I'm a first time user of prolog and I've been using it for about a week now, learning about the various basics from here, which has been helpful to a degree. I have been designing a database lookup for a fictional game title, where by the user will be able to consult the bestiary to obtain the statistics of a given character in the game. Sounds like an awesome project if you ask me :)

I've been working on learning about "frames" to describe my nodes and how to use them so the format I'll use is going to be something along these lines. The latest item I've been learning about is "Numbers" in prolog and how they work, it seemed pretty simple enough in the tutorials until I wanted to use it in a more dynamic way.

I have the following set up for my data nodes. (pseudo code)

Frame: name(character), parent(none), base_stat(8.0).

Frame: name(npc), parent(character).

Frame: name(enemy), parent(npc).

Frame: name(red_faction), parent(enemy), atk_bonus(1.5), spd_bonus(1.25), def_bonus(0.25).

Frame: name(blue_faction), parent(enemy), atk_bonus(1), spd_bonus(1), def_bonus(1).

Frame: name(fire_warrior), parent(red_faction), level(1.0), holding(nothing).

Frame: name(water_consort), parent(blue_faction), level(1.0), holding(nothing).

When consulted I want to return the following statistics to the user: attack(?), speed(?), defence(?).

I was hoping to use an equation to work out the "?" areas of my program, to which the following equation would be used "(base_stat + level) * stat_bonus". So something like this:-

compute(attribute) :-
    stat_bonus(stat bonus from parent node for required stat example:atk_bonus)
    base_stat(value from the character node's base_stat)
    level(value from the character we are looking up: eg water_consort lvl1)
    attribute is (level+base_stat) * stat_bonus

I came up with this structure after a brief explanation from a tutor regarding algebraic approaches to numbers in prolog. There example went something like:-

student(jack, 25).
student(jill, 21).

avg_Score(Z) :-
    student(jack, X),
    student(jill, Y),
    Z = (X + Y)/2.

I understand that I need to extract the values for X and Y, and if I want to ever reconsult those values I'll have to repeat that extraction to use them.

Now going back to my little project, I have a number of faction nodes (full diagram has 3, but there's no point going beyond two for this as I assume what ever process is needed can be applied N times), how do I get the information for my calculation?

Assuming I were to request the data for the fire warrior, my system wants to calculate a stat (let's say attack) and it consults the equation I have. I'll need the following information:

  1. the attack bonus given by the parent node "red_faction"
  2. the base_stat given by the g.g.grandparent node "character"
  3. the instance node's level

The example my tutor gave me will only work if I am to hard code in my request for everything and is very limited. Is there a method that I can use for "N" factions and "M" character instances for each faction? I would bother her again but she isn't free until after Easter, and I'm looking to finish this sometime over the weekend. From what I can tell Prolog does inheritance quite well, but I have no idea how to solve this.

Thank you in advance for any constructive input you can give.

Daniel Lyons
  • 22,421
  • 2
  • 50
  • 77

1 Answers1

3

Welcome to Prolog! I'm glad you're giving it a shot, and I hope you find it serves your imagination well, since you seem to have a lot!

You're using a lot of strange terminology here for Prolog. I'm not clear on what you mean by "frame" or "node" or "extract" and I'm pretty sure you're using "consult" in an off way. I don't know if this is because the tutorial was written for SICStus or if it's just the usual confusion. I also wouldn't normally think of doing inheritance in Prolog, but that part of this I think I actually do understand. So I hope you'll play along and we'll see if I manage to answer your question anyway.

First things first. It's not a big deal but average score should be implemented like this:

avg_score(Z) :-
  student(jack, X),
  student(jill, Y),
  Z is (X + Y) / 2.   % <-- note the use of 'is' instead of '='

This is a common beginner mistake. Prolog doesn't evaluate algebra by default, so when you say something like (X + Y) / 2 all you're really doing is building an expression, no different than div(plus(X, Y), 2). The one difference, really, is that is knows how to evaluate algebraic expressions like (X + Y) / 2 but not div(plus(X, Y), 2). (I'll pre-warn you that you'll find Prolog makes a lot more sense if you start using clpfd from the start, and use #= instead of is.)

No, what's unhelpful about avg_score, of course, is that jack and jill are embedded within it. Someday you'll probably want to calculate the average scores of all your students. First let's calculate the average:

average(L, Average) :-
  sum(L, Sum),
  length(L, Length),
  Average is Sum / Length.

% this is overly complex because I wanted to show you 
% the tail recursive version
sum(L, Sum) :- sum(L, 0, Sum).

sum([], Sum, Sum).
sum([X|Xs], Acc, Sum) :- Acc1 is X + Acc, sum(Xs, Acc1, Sum).

Now we can get the average of all the students like this:

avg_score(Z) :-
  findall(X, student(_, X), Scores),
  average(Scores, Z).

I don't happen to know if this relates to what you're asking or not, but it seems like it might be helpful so there it is.

Of course, another possibility is to parameterize things. We could take the original code and parameterize it by student:

avg_score(Student1, Student2, Z) :-
  student(Student1, X),
  student(Student2, Y),
  Z is (X + Y) / 2.

This seems like it might be more pertinent to querying the bestiary. Rather than asking attack(X) you would ask attack(fire_warrior, X).

I hate to send you off to Logtalk when you're so new to Prolog, but I suspect it might hold some of the answers you're looking for. It's particularly good at handling inheritance in a way that vanilla Prolog probably isn't. But it can be a big diversion. For instance, you could handle the inheritance chain for querying the attack stat like this:

% this is the inheritance tree
parent_class(character, base).
parent_class(npc, character).
parent_class(enemy, npc).
parent_class(red_faction, enemy).
parent_class(blue_faction, enemy).
parent_class(fire_warrior, red_faction).
parent_class(water_consort, blue_faction).

% these are the attack bonuses
attack_bonus(base, 0).
attack_bonus(red_faction, 1.5).
attack_bonus(blue_faction, 1).

calc_attack_bonus(X, Bonus) :-
  attack_bonus(X, Bonus), !.
calc_attack_bonus(X, Bonus) :-
  \+ attack_bonus(X, Bonus),
  parent_class(X, Parent),
  calc_attack_bonus(Parent, Bonus).

This appears to work for some basic queries:

?- calc_attack_bonus(fire_warrior, Bonus).
Bonus = 1.5.

?- calc_attack_bonus(character, Bonus).
Bonus = 0.

I don't know if you want this behavior or not:

?- calc_attack_bonus(tree, Bonus).
false.

It's easy to fix if not (if parent_class fails, unify Bonus with 0, otherwise recur).

This is not, however, all that extensible. A more extensible approach might be this:

% these are the attack bonuses
attribute(attack_bonus, base, 0).
attribute(attack_bonus, red_faction, 1.5).
attribute(attack_bonus, blue_faction, 1).

% base stats
attribute(base_stat, character, 8.0).

attribute(level, fire_warrior, 1.0).

Now we can write the rules we need without a lot of pain:

calc_attribute(X, Attribute, Value) :-
  attribute(X, Attribute, Value), !.
calc_attribute(X, Attribute, Value) :-
  \+ attribute(X, Attribute, Value),
  parent_class(X, Parent),
  calc_attribute(Parent, Attribute, Value).

And now attack becomes easy:

attack(X, Value) :-
  calc_attribute(X, attack_bonus, Bonus),
  calc_attribute(X, base_stat, Base),
  calc_attribute(X, level, Level),
  Value is (Level + Base) * Bonus.

We need to generalize a bit more to get to where we can write compute though, because ideally, we want to say something like compute(fire_warrior, attack, Value). For that to happen, we need to know that attack_bonus is related to attack and we don't know that. Let's restructure attribute a little:

% these are the attack bonuses
attribute(base,         bonus(attack), 0).
attribute(red_faction,  bonus(attack), 1.5).
attribute(blue_faction, bonus(attack), 1).

% base stats
attribute(character, base_stat, 8.0).

attribute(fire_warrior, level, 1.0).

Now we're cooking:

compute(X, Attribute, Value) :-
  calc_attribute(X, bonus(Attribute), Bonus),
  calc_attribute(X, base_stat, Base),
  calc_attribute(X, level, Level),
  Value is (Level + Base) * Bonus.

And behold:

?- compute(fire_warrior, attack, Value).
Value = 13.5.

I hope that's what you wanted. :)

Big Edit

For fun I thought I'd see what this would be like with Logtalk, an object-oriented extension language for Prolog. I'm very new to Logtalk, so this may or may not be a good approach, but it did "do the trick" so let's see if it's more along the lines of what you're wanting. First, the base object:

:- object(base).

  :- public(base_stat/1).
  :- public(attack/1).
  :- public(bonus/2).
  :- public(level/1).
  :- public(compute/2).

  bonus(attack, 0).
  base_stat(0).
  level(0).

  compute(Attribute, Value) :-
      ::bonus(Attribute, Bonus),
      ::base_stat(Base),
      ::level(Level),
      Value is (Level + Base) * Bonus.

:- end_object.

This is basically defining the facts we're going to store about each thing, and how to compute the property you're interested in. Next we establish the object hierarchy:

:- object(character, extends(base)).
    base_stat(8.0).
:- end_object.

:- object(npc, extends(character)).
:- end_object.

:- object(enemy, extends(npc)).
:- end_object.

:- object(red_faction, extends(enemy)).
    bonus(attack, 1.5).
    bonus(speed, 1.25).
    bonus(defense, 0.25).
:- end_object.

:- object(blue_faction, extends(enemy)).
    bonus(attack, 1).
    bonus(speed, 1).
    bonus(defense, 1).
:- end_object.

:- object(fire_warrior, extends(red_faction)).
    level(1.0).
    holding(nothing).
:- end_object.

:- object(water_consort, extends(blue_faction)).
    level(1.0).
    holding(nothing).
:- end_object.

Now using this is pretty much a snap:

?- fire_warrior::compute(attack, X).
X = 13.5.

?- water_consort::compute(attack, X).
X = 9.0.

I hope this helps!

Daniel Lyons
  • 22,421
  • 2
  • 50
  • 77
  • Wow, the beginning was an interesting read unto itself, running trace to see how the "predicate(list[], Variables, Variable)" query was working all the way through to making a more refined query. – Student - Serin Mar 23 '13 at 16:58
  • I'm looking into the Logtalk recommendations and it looks like a hell of a read tonight. Seeing your examples I can see that it is just the addon, and style of programming that I need. You can see the relationships clearly, and as I look at my semantic diagram I can already understand how it will all look. You may not understand what a Frame is, but again Logtalk works well to translate the visual aspect into code. I'm really excited to look further into this tonight. – Student - Serin Mar 23 '13 at 17:05
  • You deserve a well earned drink for this explanation, because without those examples I couldn't have seen the relevance. – Student - Serin Mar 23 '13 at 17:06
  • @user2196256 I'm really glad I was able to help! I'm looking forward to seeing the evolution of your project. – Daniel Lyons Mar 24 '13 at 04:44
  • @Student-Serin any particular reason why you unaccepted the answer? – Daniel Lyons Mar 29 '13 at 15:51
  • Sorry about that, I did completely wrong thing, I wasn't awake when deleting a post earlier. Here's the latest on my logtalk experience by the way, I wish there were some more tutorials on it about, http://stackoverflow.com/questions/15706681/prolog-swi-logtalk-how-do-i-load-my-own-project-files its a question about loading files. I am following you on google by the way, added you earlier, saw your site and was impressed by the mining articles. – Student - Serin Mar 29 '13 at 23:03
  • Accidents happen. :) If I have some time tonight I'll try and answer your new question. Sometimes the author of Logtalk shows up to answer questions about Logtalk though, so hopefully he will send you in the right direction. I'm very inexperienced at Logtalk myself. – Daniel Lyons Mar 29 '13 at 23:14