Rossman Posted November 1, 2017 Share Posted November 1, 2017 I'm not under any illusions that anybody finds this interesting or relevant. I'm documenting it here for my own edification and education: many of you have been helpful to explain / clarify what I'm seeing and experiencing, and I thank you all for that. Per my other thread, I've got my Pascal sprite test code doing what I want it to do. It isn't a fully functioning UI, but it demonstrates that I can get the sprites to behave as I want them to. As @apersson850 pointed out, the sprite logic is pretty sophisticated. I have the cards doing all kinds of gymnastics with precision, so I have to agree. From here, it's all in the details. Those details will become unmanageable pretty quickly in the game code logic as it exists. So, I'm looking to refactor the game logic code. An in-place re-write will quickly become unmanageable, so I started playing with Pascal units tonight. I figure I can harvest code out of the game logic into a unit, procedure (and function!) by procedure, until I've strangled the old code. I do not want to criticize the communication style of the authors of the TI Pascal text. And in the grand scheme of things, it didn't take me long to figure it out. But the documentation in the Compiler manual, section 4.5 on Units (pages 100-105) references examples without parameters. This was a little frustrating, but the compile errors described the expected behaviors. If I'm understanding this correctly, procedures declared in units are treated as Pascal forwards. The declaration in the INTERFACE section has the full procedure signature (including params). The declaration in the IMPLEMENTATION section has no parameters, because it is unnecessary. The parameters declared in the INTERFACE section are accessible. So, if we have: INTERFACE procedure yourproc(yourparm : string); IMPLEMENTATION procedure yourproc; begin writeln(yourparm); end; end. This would work fine. Your parameters are declared in the INTERFACE, but not redundantly in the IMPLEMENTATION. This appears to be a clean implementation of the Pascal forward. One quirk I think I have experienced is that I'm not able to pass an array in the procedure signature. However, if I have a typed array, it works. Specifically, I get a compile error if I have: procedure loadsuits(var suitname : array[0..3] of string); However, if I do this, it works. UNIT cardcode; INTERFACE type csuit = array[0..3] of string; procedure loadsuits(var suitname : csuit); IMPLEMENTATION procedure loadsuits; { NOTICE NO PARAMETER DECLARATION!! } begin suitname[0] := 'Hearts'; suitname[1] := 'Diamonds'; suitname[2] := 'Clubs'; suitname[3] := 'Spades'; end; END. { IMPLEMENTATION } It works just fine. It's callable and I can write out an array that I pass in. Getting it to compile was a matter of having the library visible to the compiler. I took the cave-man approach of simply having the unit cardcode (cleverly, in a compiled program called cardcode.code) on the compiler disk, and it compiled. But it didn't run because at runtime, the code couldn't find the library. If I'm reading it right, page 105 of the compiler manual warns that run-time access to units is different from compile time access. I can confirm this! I believe I was a little bit stymied by some of the helper modules that I am using. Without getting into too much detail, my Pascal boot disk is a derivative of disk 712a. (I can clarify this if anybody has questions). That changes the searched libraries. I was fortunate to remember the eXecute, L=[library file] as a Thing. I created a *USERLIB.TEXT file, ran eXecute L=WORK:*USERLIB.TEXT, and my call works at runtime. It only took about 3 hours, and my little Unit call test is working. My expectation (hope) is that I can decouple card logic from game logic from UI logic, and build a better product this way. 4 Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 2, 2017 Author Share Posted November 2, 2017 It's always a bit sad when you reply to your own post. So it goes. I moved more card deck logic over to the unit. No problems. In the process, I'm creating better procedure structure (procedures are modular, meaning parameters, no globals), and creating crude unit tests (and yes the unit tests have exposed mistakes). The game logic I coded last month is my first Pascal code in a long, long time. No surprise, it's a monolith. With the Unit, I will be able to apply the strangler pattern to unwind that monolith. This (for me, anyway) is a big deal. 2 Quote Link to comment Share on other sites More sharing options...
+Schmitzi Posted November 2, 2017 Share Posted November 2, 2017 It's always a bit sad when you reply to your own post. So it goes. No need to be sad This is very interesting, and you are under investigation Quote Link to comment Share on other sites More sharing options...
+Ksarul Posted November 2, 2017 Share Posted November 2, 2017 Several of us are watching the process with interest. . . 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 2, 2017 Share Posted November 2, 2017 I will be able to apply the strangler pattern to unwind that monolith. Thanks for this link. I was not aware of this method but I like it alot. 1 Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 3, 2017 Author Share Posted November 3, 2017 Hey, all. Thanks for the kind words. I'm posting all of this here because (a) I learn from many of you and (b) in future some members of this community might try stuff in Pascal and they might benefit. Or just do a slow hand clap. My prior post really was tongue-in-cheek. Have you ever sent a highly analytical email that you thought was deeply insightful, only to reply to yourself before anybody else could? It's one of those Bugs Bunny cartoon moments when the crickets are chirping. No worries. But thanks for acknowledging. Anyway... I moved the shuffle logic for the shoe (4 x 52 card decks) into the Unit code. This only works if the shuffle is random. The USES RANDOM only needs to be in the IMPLEMENTATION section of the Unit as it's local. The RANDOMIZE procedure can be executed at the procedure level as it randomizes based on clock value. I moved on to one of the final card (e.g., non-game) functions, dealing a card. Functions in Units are executed as forwards just as they are with procedures, and are implemented as you'd expect. The full signature is defined in the INTERFACE section, but not in the IMPLEMENTATION section. That means there is a type definition in the INTERFACE but not the IMPLEMENTATION. By way of example: INTERFACE function dealacard(var pointer : integer): integer; IMPLEMENTATION function dealacard; No parameters, no return type in the IMPLEMENTATION block. For the more astute among you (which is pretty much everybody who is reading this), yes, there is an obvious flaw with this, as it's a singular number fountain that, oddly, requires two integers. It started life as a different type of function. I will refactor this into a singular number fountain. Which brings me to this: Thanks for this link. I was not aware of this method but I like it alot. I was really happy to read this, @TheBF. I apply the strangler pattern to complex legacy enterprise applications. I.e., there's a long-lived legacy product that, for various reasons, we want to migrate to a more current technology. Instead of wholesale big-bang replacement, we slowly strangle the core functionality, iteratively redirecting a monolith to call new code blocks in new technology, bypassing / deprecating / deleting existing logic that does the same. It works at the enterprise level with concerted effort. It's been uniquely satisfying to apply it to a simple card game in Pascal, swapping a monolithic structure for procedures via interface. Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 4, 2017 Author Share Posted November 4, 2017 (edited) I moved the card logic into a UNIT in a single exercise. "Single exercise" is generous, because I had to identify all of the procedures in the monolith that consumed global variables (such as arrays) related to card logic, so I had a few rounds of compile - error - fix. But it worked, and I had it completed in about 2 hours. Thank you @Tursi. #classic99 Yes, mame is on my near-term radar. But I felt I had to complete this exercise, and classic99 has not failed me since I figured out the CONST overload. And for all I know, that is a p-code limitation, not an emulator limitation. That, too, is on my list of things to confirm or deny - which is why I have prioritized the mame installation. Back to the code. Strangling code is never a simple proposition. Among other things, I was a little bit worried about variable name conflicts. But then I read this on page 77 of my academic reference, "Oh! Pascal!": procedure SumSquares (First, Second: integer); ... "We can call SumSquares with any two arguments that represent integer values - numbers, function calls, expressions, variables, etc. Any similarity between names is purely coincidental." [My emphasis] ... SumSquares(First, Second); This matters because... 1. The logic that was in the main program has been moved to a procedure. The main program now consists of a single statement. 2. I am migrating variable declarations from global to the procedure, to improve code modularity. 3. I have transitive dependencies in my procedures as a result of the globals and as a result of comingling game logic and UI logic. That's an own goal I never, ever should have scored. The part that I have in bold, above, means I can more easily encapsulate my code. Huge relief. Fortunately, the card logic was sufficiently isolated, so I was able to migrate that to a separate unit, and it works. I have deleted the card logic from the monolith, and my unit calls to define a randomized quad of 4-suite, 52-card decks are behaving as expected. I have started to map the game and UI logic. This will be a bit more involved. Although I adopted more modular thinking in the procedures and methods, I may have more tightly coupled the game and UI logic. I'll know more as I unwind this. Edited November 4, 2017 by Rossman Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 5, 2017 Author Share Posted November 5, 2017 So this was interesting. I am creating a second UNIT, this time for the UI code. I hope to create a crude implementation of the model-view-controller pattern with my text-based UI before introducing the sprite UI. In my code, I now have: USES {$U CARDCODE.CODE} cardcode, {$U CDFTUI.CODE} cdftui; I have a *USERLIB.TEXT file, with two lines: WORK:CARDCODE.CODE WORK:CDFTUI.CODE I added my new unit (CDFTUI.WORK) to this file, eXecuted L=WORK*USERLIB.TEXT, and the new UNIT was picked up during compile without problem. Runtime was entirely another matter. My new UNIT wasn't found at runtime. I started experimenting. First, I swapped the sequence in the USES declaration. Interestingly enough, when I did this, my new UNIT was found at runtime, but my old library wasn't! I tried fiddling with the USES block, but then I went back to the *USERLIB.TEXT file. There is some documentation on *USERLIB.TEXT in AtariAge. That gave me the idea of using a different library file entirely. So I created one called *CDFLIB.TEXT with the same content as my *USERLIB.TEXT, eXecuted L=WORK:*CDFLIB.TEXT, and then ran the code again. And this time, both UNIT libraries were picked up at runtime, and performed as expected. Good news that I can now fill out my second UNIT with more of the UI code, but I seem to have a mystery problem on my hands. I have what appears to be misbehavior of Pascal reading *USERLIB.TEXT at runtime. I'm not sure if this is a function of *USERLIB.TEXT, or the emulator (I've not had a chance to try this on real iron, and no I still don't have MAME installed... yes it's on my list!) If anybody has experienced something similar please let me know. I'm probably going to be heads down on code for a few days to complete the migration of the text UI code. I'll create a more robust collection of test environments (Classic99, MAME, real iron) before creating the Sprite UI. Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 5, 2017 Author Share Posted November 5, 2017 I've strangled all but one UI procedure to a new UNIT. The hold-out procedure combines card logic, game logic, and UI logic. Not a lot of lines, but bad none-the-less. Rather than solve for this directly, I'm thinking I might create another UNIT for game logic. Doing so might help me isolate other bad citizen procedures, which will help me refactor the core. Quote Link to comment Share on other sites More sharing options...
Rossman Posted November 6, 2017 Author Share Posted November 6, 2017 Moved all of the UI logic to its own UNIT. I'm surprised at how differently I see that code now. Lots of opportunity for consolidation and simplification. #freefromthemonolith Since there will be some text UI even with the sprite UI, I'll clean this up first. Quote Link to comment Share on other sites More sharing options...
apersson850 Posted November 7, 2017 Share Posted November 7, 2017 You're right about that referencing units at compile time and run time isn't necessarily the same thing. At least not if they aren't in *SYSTEM.LIBRARY. It also makes a difference if a unit references other units in the interface or implementation section. It's less critical if the reference is in the implementation section, as it's then private to that unit. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.