score.t | documentation |
#charset "us-ascii" /* * Copyright (c) 2000, 2006 by Michael J. Roberts. All Rights Reserved. * * TADS 3 Library - scoring * * This module defines objects related to keeping track of the player's * score, which indicates the player's progress through the game. */ /* include the library header */ #include "adv3.h" /* ------------------------------------------------------------------------ */ /* * An Achievement is an object used to award points in the score. For * most purposes, an achievement can be described simply by a string, * but the Achievement object provides more flexibility in describing * combined scores when a set of similar achievements are to be grouped. * * There are two ways to use the scoring system. * * 1. You can use a mix of string names and Achievement objects for * scoring items; each time you award a scoring item, you call the * function addToScore() to specify the achievement (by name or by * Achievement object) and the number of points to award. You can also * call the method addToScoreOnce() on an Achievement object to award * the scoring item, ensuring that the item is only awarded once in the * entire game (saving you the trouble of checking to see if the event * that triggered the scoring item has happened before already in the * same game). If you do this, you MUST set the property * gameMain.maxScore to reflect the maximum score possible in the game. * * 2. You can use EXCLUSIVELY Achievement objects to represents scoring * items, and give each Achievement object a 'points' property * indicating the number of points it's worth. To award a scoring item, * you call the method awardPoints() on an Achievement object. If you * use this style of scoring, the library AUTOMATICALLY computes the * gameMain.maxScore value, by adding up the 'points' values of all of * the Achievement objects in the game. For this to work properly, you * have to obey the following rules: * *. - use ONLY Achievement objects (never strings) to award points; *. - set the 'points' property of each Achievement to its score; *. - define Achievement objects statically only (never use 'new' to *. create an Achievement dynamically) *. - if an Achievement can be awarded more than once, you must override *. its 'maxPoints' property to reflect the total number of points it *. will be worth when it is awarded the maximum number of times; *. - always award an Achievement through its awardPoints() or *. awardPointsOnce() method; *. - there exists at least one solution of the game in which every *. Achievement object is awarded */ class Achievement: object /* * The number of points this Achievement scores individually. By * default, we set this to nil. If you use the awardPoints() or * awardPointsOnce() methods, you MUST set this to a non-nil value. * * If you set this to a non-nil value, the library will use it * pre-compute the maximum possible score in the game, saving you the * trouble of figuring out the maximum score by hand. */ points = nil /* * The MAXIMUM number of points this Achievement can award. This is * by default just our 'points' value, on the assumption that the * achievement is scored only once. The library uses this value * during pre-initialization to compute the maximum possible score in * the game. */ maxPoints = (points) /* * Describe the achievement - this must display a string explaining * the reason the points associated with this achievement were * awarded. * * Note that this description can make use of the scoreCount * information to show different descriptions depending on how many * times the item has scored. For example, an achievement for * finding various treasure items might want to display "finding a * treasure" if only one treasure was found and "finding five * treasures" if five were found. * * In some cases, it might be desirable to keep track of additional * custom information, and use that information in generating the * description. For example, the game might keep a list of * treasures found with the achievement, adding to the list each * time the achievement is scored, and displaying the contents of * the list when the description is shown. */ desc = "" /* show myself in a full-score listing */ listFullScoreItem() { /* show the number of points I'm worth */ gLibMessages.fullScoreItemPoints(totalPoints); /* show my description */ desc; } /* * The number of times the achievement has been awarded. Each time * the achievement is passed to addToScore(), this is incremented. * Note that this is distinct from the number of points. */ scoreCount = 0 /* * the number of points awarded for the achievement; if this * achievement has been accomplished multiple times, this reflects * the aggregate number of points awarded for all of the times it * has been accomplished */ totalPoints = 0 /* * Add this achievement to the score one time only, awarding the * given number of points. This can be used to score an achievement * without separately tracking whether or not the achievement has * been accomplished previously. If the achievement has already been * scored before, this will do nothing at all; otherwise, it'll score * the achievement with the given number of points. Returns true if * we do award the points, nil if not (because we've awarded them * before). */ addToScoreOnce(points) { /* if I've never been scored before, score me now */ if (scoreCount == 0) { /* add the points to the score */ addToScore(points, self); /* tell the caller we awarded the points as requested */ return true; } else { /* tell the caller we didn't do anything */ return nil; } } /* * Award this Achievement's score, using the score value specified in * my 'points' property. */ awardPoints() { /* add me to the score, using my 'points' property */ addToScore(points, self); } /* * Award this Achievement's score, but ensure that we're never * awarded more than one time. If this Achievement has already been * awarded, this does nothing at all. Returns true if we do award * the points, nil if not (because we've awarded them before). */ awardPointsOnce() { /* award my 'points' value only if we haven't score before */ return addToScoreOnce(points); } ; /* * Generic text achievement. When we add an achievement to the full * score list and the achievement is a simple string description, we'll * create one of these to encapsulate the achievement. */ class SimpleAchievement: Achievement /* create dynamically with a given string as our description */ construct(str) { desc_ = str; } /* show my description */ desc { say(desc_); } /* my description string */ desc_ = '' ; /* * List interface for showing the full score list */ fullScoreLister: Lister showListPrefixTall(itemCount, pov, parent) { /* showt he full score list intro message */ gLibMessages.showFullScorePrefix; } /* every achievement is listed */ isListed(obj) { return true; } /* each item counts as a singular object grammatically */ listCardinality(obj) { return 1; } /* achievements have no containment hierarchy */ getContents(obj) { return []; } getListedContents(obj, infoTab) { return []; } showContentsList(pov, obj, options, indent, infoTab) { } showInlineContentsList(pov, obj, options, indent, infoTab) { } /* show an item */ showListItem(obj, options, pov, infoTab) { obj.listFullScoreItem(); } ; /* * Score notification daemon handler. We'll receive a * checkNotification() call each turn; we'll display a notification * message each time the score has changed since the last time we ran. */ scoreNotifier: PreinitObject /* the score as it was the last time we displayed a notification */ lastScore = static (libScore.totalScore) /* we've never generated a notification about the score before */ everNotified = nil /* daemon entrypoint */ checkNotification() { /* * if the score has changed since the last time we checked, * possibly generate a notification */ if (libScore.totalScore != lastScore) { /* only show a message if we're allowed to */ if (libScore.scoreNotify.isOn) { local delta; /* calculate the change since the last notification */ delta = libScore.totalScore - lastScore; /* * generate the first or non-first notification, as * appropriate */ if (everNotified) gLibMessages.scoreChange(delta); else gLibMessages.firstScoreChange(delta); /* * note that we've ever generated a score change * notification, so that we don't generate the more * verbose first-time message on subsequent * notifications */ everNotified = true; } /* * Remember the current score, so that we don't generate * another notification until the score has changed again. * Note that we note the new score even if we aren't * displaying a message this time, because we don't want to * generate a message upon re-enabling notifications. */ lastScore = libScore.totalScore; } } /* execute pre-initialization */ execute() { /* initialize the score change notification daemon */ new PromptDaemon(self, &checkNotification); } ; /* * Add points to the total score. This is a convenience function that * simply calls libScore.addToScore_(). */ addToScore(points, desc) { /* simply call the libScore method to handle it */ libScore.addToScore_(points, desc); } /* * The main game score object. */ libScore: PreinitObject /* * Add to the score. 'points' is the number of points to add to the * score, and 'desc' is a string describing the reason the points * are being awarded, or an Achievement object describing the points. * * We keep a list of each unique achievement. If 'desc' is already * in this list, we'll simply add the given number of points to the * existing entry for the same description. * * Note that, if 'desc' is an Achievement object, it will match a * previous item only if it's exactly the same Achievement instance. */ addToScore_(points, desc) { local idx; /* * if the description is a string, encapsulate it in a * SimpleAchievement object */ if (dataType(desc) == TypeSString) { local newDesc; /* * look for an existing SimpleAchievement in our list with * the same descriptive text - if we find one, reuse it, * since this is another instance of the same group of * achievements and thus can be combined into the same * achievement object */ newDesc = fullScoreList.valWhich( { x: x.ofKind(SimpleAchievement) && x.desc_ == desc }); /* * if we didn't find it, create a new simple achievement to * wrap the descriptive text */ if (newDesc == nil) newDesc = new SimpleAchievement(desc); /* * for the rest of our processing, use the wrapper simple * achievement object instead of the original text string */ desc = newDesc; } /* increase the use count for the achievement */ desc.scoreCount++; /* add the points to the total */ totalScore += points; /* try to find a match in our list of past achievements */ idx = fullScoreList.indexOf(desc); /* if we didn't find it, add it to the list */ if (idx == nil) fullScoreList.append(desc); /* * combine the points awarded this time into the total for this * achievement */ desc.totalPoints += points; } /* * Explicitly run the score notification daemon. */ runScoreNotifier() { /* explicitly run the notification */ scoreNotifier.checkNotification(); } /* * Show the simple score */ showScore() { /* * Show the basic score statistics. Use the appropriate form of * the message, depending on whether or not there's a maximum * score value. */ if (gameMain.maxScore != nil) gLibMessages.showScoreMessage(totalScore, gameMain.maxScore, libGlobal.totalTurns); else gLibMessages.showScoreNoMaxMessage(totalScore, libGlobal.totalTurns); /* show the score ranking */ showScoreRank(totalScore); } /* * show the score rank message */ showScoreRank(points) { local idx; local tab = gameMain.scoreRankTable; /* if there's no rank table, skip the ranking */ if (tab == nil) return; /* * find the last item for which our score is at least the * minimum - the table is in ascending order of minimum score, * so we want the last item for which our score is sufficient */ idx = tab.lastIndexWhich({x: points >= x[1]}); /* if we didn't find an item, use the first by default */ if (idx == nil) idx = 1; /* show the description from the item we found */ gLibMessages.showScoreRankMessage(tab[idx][2]); } /* * Display the full score. 'explicit' is true if the player asked * for the full score explicitly, as with a FULL SCORE command; if * we're showing the full score automatically in the course of some * other action, 'explicit' should be nil. */ showFullScore() { /* show the basic score statistics */ showScore(); /* list the achievements in 'tall' mode */ fullScoreLister.showListAll(fullScoreList.toList(), ListTall, 0); } /* * Vector for the full score achievement list. This is a list of * all of the Achievement objects awarded for accomplishments so * far. */ fullScoreList = static new Vector(32) /* the total number of points scored so far */ totalScore = 0 /* * current score notification status - if on, we'll show a message at * the end of each turn where the score changes, otherwise we won't * mention anything */ scoreNotify = scoreNotifySettingsItem /* * Compute the sum of the maximum point values of the Achievement * objects in the game. Point values are optional in Achievement * objects; if there are no Achievement objects with non-nil point * values, this will simply return nil. */ calcMaxScore() { local sum; local found; /* start with a running total of zero */ sum = 0; /* we haven't found any non-nil point values yet */ found = nil; /* * Run through all of the Achievement objects to see if we can * derive a maximum score for the game. */ forEachInstance(Achievement, function(obj) { local m; /* * If this object has a non-nil maxPoints value, add it to * the running total. */ if ((m = obj.maxPoints) != nil) { /* add this one to the sum */ sum += m; /* note that we found one with a non-nil point value */ found = true; } }); /* * If we found any Achievements with point values, return the sum * of those point values; otherwise, return nil. */ return (found ? sum : nil); } /* execute pre-initialization */ execute() { /* register as the global score handler */ libGlobal.scoreObj = self; } ; /* settings item for score notification mode */ scoreNotifySettingsItem: BinarySettingsItem /* the "factory setting" for NOTIFY is ON */ isOn = true /* our configuration file variable ID */ settingID = 'adv3.notify' /* show our description */ settingDesc = (gLibMessages.shortNotifyStatus(isOn)) ;
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3
Generated on 5/16/2013 from TADS version 3.1.3