If you think about it a little, you'll likely come up with the items that have to be added to the record. First, I've made no allowance for changing innings. DUH! (Actually, I did know about that. As I've mentioned before, it is perfectly appropriate to assemble a clean structure and then make it do all the tricks you want it to perform. This should become a little more clear as I continue.) Second, and somewhat related to the first item, I've made no allowance for constructing the unique key that identifies each record in the participant_game table. As you'll see, since I've decided to build in support for multiple users scoring the same game, there are some additional considerations attendant to that that I'll have to work through. At least part of the moral of this little story is that this is always the way things go. While I purposefully left off changing the inning and thus constructing the key values in earlier incarnations of the script, the addition of multi-user capability places demands on those identifying elements that did not exist in earlier schemes. The flip side of the story is thgat if you pay attention to making the underlying execution model as clean as possible your ability to successfully incorporate application enhancements will be maximized. This is very much the same point I made when I was discussing database design. This is not coincidental, and is an element of the perspective that leads to a pure object-oriented approach in which an object, its characteristics, and the operations it performs are coupled. But that's kind of blue-sky philosophical stuff, and I'm digging a trench here. On a pragmatic level, my argument is, as always, that it is the nature of information systems to evolve. If you make the conceptual underpinnings of the various elements of the system as clean as possible, you will be able to accomodate the changes wrought by that evolution much more readily.
For the most part, adding these elements is largely mechanical in the sense that a major portion of the implementation effort involves keeping track of everything that is getting passed around between incarnations of the script. You'll see what I mean shortly ... in the next version of the script one of the things I'm going to do is look at ways to put most, if not all, of the hidden fields into a big container and pass that around, but we'll get to that when the times comes
If you recall, the original key column in the participant_game table was 10 characters in length, representing the concantenation of game_id, inning, and inning_order, and uniquely identifying each individual row in the table. That's obviously not going to work with multiple users, because there would be no way to distinguish which entry came from which user, and therefore no way to accurately represent the stream of entered records from individual users. (Actually, that's not quite true. I could read something from the environment and use that as an identifier as I discussed in another context a few sections ago, but many of the same arguments that led me to reject that strategy at that time would also apply here.) I gave very brief consideration to using the session_id in this fashion, as that would not require addtional elements to be introduced, but the downstream requirements for maintaining that scheme are significant enough to reject it out of hand. (For example, we could no longer simply periodically delete the session file. We'd have to make sure that the session_id was unique within any given game, and we'd probably want to somewhere maintain an association between the entry person and session_id.) It makes far more sense simply to have the user enter a user name, then use that in the key. This also provides basic support for user-based password authentication, which I'll be starting to adopt in coming iterations.
print header("text/html"),
('<html>');
print ("<H1>User and Game Identification, and Entry Form Configuration</H1>");
print ("<p><form>");
print ("Enter your Username (up to 5 characters):
<input type='text' name='user_name' width='5'><br>");
print ("Enter the Game ID for this game:
<input type='text' name='game_id' width='5'><br><br>");
The other two data elements required to make sure that each participant_game record is unique are, of course, inning and inning_order. The scalars holding the values for these elements are initialized in the get_session subroutine along with the $session_id and $pass scalars. $inning is initialized to the value '01' while $inning_order is initialized to '001'. You might ask, why initialize the scalars as characters ... why not just treat them as integers? If you look at the store subroutine and think about it for a bit you might see the reason. When a numeric value is concantenated onto a string the relative positon of the first significant digit of that value is always going to occupy the leftmost positon in the resultant string. This can cause problems when you are trying to put items into an appropriate order ... I wouldn't be surprised if you have noticed that if you were to name files by a convention in which the unique part of the name, to the left of the period, is a number you cannot sort that directory by the name of the file without having an order something like this ...
1.txt
11.txt
12.txt
.
.
.
19.txt
2.txt
21.txt
.
.
.
More likely than not, what you really want is something like this:
01.txt
02.txt
.
.
.
11.txt
12.txt
.
.
The common way of expressing what I'm doing here is zero-filling the left-hand portion of the string, also known as stuffing the left-hand portion with zeros. Now while there are tools for this type of thing, there is no point in creating a context in which you are forced to use them, and to avoid having to use them all you have to do is initialize the scalars as strings.
Once we have the values we need, the primary concern is moving them all around. Here, for example, is the section of the store subroutine in which I print hidden fields back to the browser ...
$forms->hidden('action','enter'),
$forms->hidden('num_forms',$num_forms),
$forms->hidden("session_id",$session_id),
$forms->hidden('pass',$pass),
$forms->hidden('user_name',$user_name),
$forms->hidden('game_id',$game_id),
$forms->hidden('inning_order',$inning_order),
$forms->hidden('inning',$inning),
$forms->hidden('prev_inning',$prev_inning),
Now that I've captured the elements I need, the primary concern in managing the flow of these pages and the data entered onto them is making sure that the appropriate identifying information is written into the output file on each iteration of the script. While I'm passing all of the key information back and forth, of the elements comprising the value of the key; game_id, user_id, inning, and inning_order, in the context of a given entry session only inning and inning_order will change. The value of inning, of course, will change only occassionally, while inning_order will change with each line written.
print $forms->p("Inning:<input type='text' name='inning' width='2' value='$inning'>");
while ($j <= $num_forms) {
if ($ec[$j]) {
$k++;
print $recs $game_id.'.'.$user_name.'.'.$inning.'.'.$inning_order."ZzZ".$game_id."ZzZ".$inning."ZzZ".$inning_order."ZzZ".$ec[$j]."ZzZ".$rc[$j]."ZzZ".$pc[$j]."ZzZ".$erc[$j]."ZzZ".$et[$j]."ZzZ\n";
$inning_order++;
}
$j++;
}
if ($inning != $prev_inning) {
$inning_order='001';
$forms->delete('prev_inning');
$prev_inning=$inning;
}
That's pretty much all that has to be done to get the flow of the entry screens to handle unique users and deal with innings and the order of records within innings. Now all this script has to do is save the records in a form that incorporates the added fields. If you take a look at the store subroutine you'll probably be able to discern what I did. To be able to readily handle the key as a value in its own right and to extract the elements of which it is comprised as independent values I've seperated those components with periods ("."). As a result, in the first split of the insert_recs() subroutine, which you may recall uses "ZzZ" as a delimiter, the key field is returned as one string which is then split into the component values using the period delimiter, after which the values for those columns are stored independently. It may occur to you that this implies some degree of duplication in what is stored, and you'd be right. Each of the columns in the key are, in effect, stored twice, once in the key and once independently. I made this choice because processing power is at a relatively higher premium within the cluster than storage space. Since the duplication requires 15 bytes extra storage per record, and it would be reasonable to estimate two hundred records per inning, the duplication would add approximately 30 Kb to the database each game. Against this I balanced the fact that each extraction or concantenation operation performed as a query is run requires the execution of additional instructions, and as a result decided to use the space. Realistically, this is not all that big a deal, but does illustrate the kind of trade-offs that can be made to optimize the performance of a system in any given context. One ot two tweaks like this do not make all that much difference, but the aggregate performance increase from five or ten such tweaks can be substantial.