Setting Up Chat Loopable0:00
So chat is going to be another loopable, and I've already set it up, it's called chat. And it's going to be the chat property, and let's new it up. This chat equals new chat. And we'll register the loopable. And we're already a good chunk of the way there. Let's check out this chat loopable. So right now, this chat loopable, all it's doing is loading the conversation. And the conversation is just getting lines from a chat.txt, and it's just setting it up as a collection of pending messages. Let's just take a look at that chat.txt real quick.
up as a collection of pending messages. Let's just take a look at that chat.txt real quick. So it's just a script. We have the character who's saying it, a colon, and then lines. That's all this is. Okay, so let's set up our onTick function. public function onTick. So the premise is that Dave types out to Hal, but Hal doesn't type. Hal's messages just appear. So we need to set up a couple of properties here.
Building onTick Logic0:57
Hal's messages just appear. So we need to set up a couple of properties here. We're going to set up the public currentMessage, which is the current message being typed out. Do a public message, which is the full message that we are typing out. This would be as it's getting typed out, we're going to build up this string from this message. And then we're going to say, public speaker, we'll say, who is the current speaker? And then we're going to have a public collection of messages. These are messages that are already sent and we can print out to the screen. So if this message equals blank, well, we need to load the next message. So let's extract that out to a method, nextMessage.
So if this message equals blank, well, we need to load the next message. So let's extract that out to a protected function, nextMessage. And we can just return early. Let's figure out what this next message looks like. protected function nextMessage. So we can take the speaker and the message off of the next pending message, and say shift. And then we can set the current message equal to blank, because we haven't typed anything yet. We'll set the next message to type equal to the message. We'll set the next speaker equal to the speaker.
We'll set the next message to type equal to the message. We'll set the next speaker equal to the speaker. And then we will pause for 10 ticks. We're going to just take a break and let the conversation happen naturally. Okay, so if the message is blank, load up the next message. Well, what happens if the message isn't blank? Well, if this speaker equals how, how doesn't type. So we can just say this on nth tick 10, because we're going to give it a second for how to quote unquote listen and process the message. We'll say this messages push this speaker this message.
quote unquote listen and process the message. We'll say this messages push this speaker this message. And then we'll just say, hey, this next message. We're ready for the next message. So all of these messages will be a little tuple of the speaker and the message that was sent. And then we can just return early from here. So at this point, the speaker is going to be Dave. So we need to type this out. So we'll say this current message is equal to a substring of this message zero string
So we need to type this out. So we'll say this currentMessage is equal to a substring of this message zero string length thisCurrentMessage plus one. So just type " the next character of the message and make the currentMessage one more character of the ultimately intended message. Now we have to consider how do we know when we're done typing the currentMessage? Well, we're done typing the currentMessage when thisCurrentMessage is equal to thisMessage. If we get to this place, well, it's the same thing as here. We push that onto the messages, and then we say we're ready for the next message, and
If we get to this place, well, it's the same thing as here. We push that onto the messages, and then we say we're ready for the next message, and we can return early. So let's review this real quick. If the message is blank, fire up the next message, get out of here. If the speaker is equal to how, wait a moment, push the next message onto the collection and hit the next message, get out of here. If the current message is equal to the message we're intending to type, push the message onto the collection, go to the next message, and get out of there. That means we're done typing.
onto the collection, go to the next message, and get out of there. That means we're done typing. Otherwise, if we get down to here, that means we're still typing. So set the currentMessage equal to the substring of the message plus one of the string length of the currentMessage, i.e. type the next character of the message. One thing we forgot to do, we forgot to initialize our messages collection, so we'll say this messages is equal to an empty collection. And that should be it for this. So let's go over to our renderer and actually print out these messages. So let's set chat equal to this, render, chat, and we'll pass the prompt.
Rendering Chat Messages4:56
So let's go over to our renderer and actually print out these messages. So let's set chat equal to this, render, chat, and we'll pass the prompt. And we'll grab this and copy it down and say renderChat. So renderChat is also going to return a collection. So let's map our messages into a formatted state. So our messages are equal to the prompt, chat, messages, and we are going to map each of these messages, and we'll say if the speaker, which is message[0], if message[0] is equal to how, then we will say this read message[0]. Otherwise, if it's Dave, we'll say this cyan message[0]. So we're going to color the speaker red if it's how, and cyan if it is Dave.
Otherwise, if it's Dave, we'll say this cyan message zero. So we're going to color the speaker red if it's How, and cyan if it is Dave. And then, secondly, we want to word wrap the actual message to the column width. So we'll say wordWrap messageOne to this column width so that we don't extend the width of the column accidentally. Okay, now let's actually put them in the right order. So we'll say map function lines. So we have two lines here, the speaker line and the word wrap line, and let's just format this text a little bit. So we'll say speakerMessages equals lines, and we're going to return a collection of
this text a little bit. So we'll say speakerMessages equals lines, and we're going to return a collection of the message. So we want to explode the lines of the message based on end of lines. We're going to prepend the speaker, and then we're going to push a blank line after the message. And then we're going to flatten it all so that we just get message lines one by one instead of a nested array. Next, we want to pretend that we are typing out this message, and this is going to take a couple of different methods and traits in order to make this happen.
Capturing Boxed Input6:54
Next, we want to pretend that we are typing out this message, and this is going to take a couple of different methods and traits in order to make this happen. So our input is going to be equal to $input, and we're going to need to import a couple of traits here. So use CapturesOutput from Chewy, and we're going to say use DrawsBoxes because we're going to fake an input that we're typing into and draw a box. And so we'll say this captureOutput, and this gets a callback. We'll say this box, there's no box title, but the box content is the $prompt value with cursor with a max width of 60. So obviously there's some things missing here.
cursor with a max width of 60. So obviously there's some things missing here. We need to implement this value with cursor, which we will shortly. But before we do that, let's actually go ahead and input equals, let's break this into lines that we can actually print out. So we'll say PHP_EOL input, and just filter any empty lines that might have come out of that in case there was an extra line break. So why are we doing it this way? Well, prompts provides this very useful box method, which draws boxes, but it writes directly to the output.
Well, prompts provides this very useful box method, which draws boxes, but it writes directly to the output. So we are using a chewy method called captureOutput, and it captures the output sent out by a method such as this and returns it to a variable so that we can use it without writing it directly. We'll get back to this value with cursor momentarily, but let's finish out this method here and now. So we have a big messages, so this should be messages, collection, but we only have so much space on the screen. So we need to slice off the messages based on the height that we have available to us.
so much space on the screen. So we need to slice off the messages based on the height that we have available to us. So we'll say messages is equal to messages.slice, and then how much height do we have? Well we have the height that we have calculated minus the headerHeight. So we should probably figure that out up here. Let's set up a new property called contentHeight, and once we've rendered the header, we can say this contentHeight is equal to this height minus this currentLineCount. We're directly rendering the header and printing out the lines. So this, which comes from the CapturesOutputTrait, counts the current lines that we've already output, and we can just subtract that from the height.
So this, which comes from the captures output trait, counts the current lines that we've already output, and we can just subtract that from the height. So now we have our contentHeight available to us. Okay, back down at our renderChat method, we can now say that we want the lastLines equal to the contentHeight, whatever much that is. Now we need to pad this out so that the input is pinned to the bottom, so we can say while messages count is less than the contentHeight, we'll say messages pre-pend an empty line so that it sort of pads out the top, that should be messages, based on the contentHeight. Okay, so now let's return messages, merge, input. So we're saying print the messages out first, then merge the input lines and we should be
Implementing Cursor Display10:01
Okay, so now let's return messages, merge, input. So we're saying print the messages out first, then merge the input lines and we should be there. Okay, let's talk about this valueWithCursor that we need to add to our app class. Okay, at the top of our app class, we're going to import something from prompts called useTypedValue. And this is useful when you're actually typing a value out to the screen. You can track that typed value doing this, but we are fake typing, but the principle remains the same. Then we just need to implement this valueWithCursor method.
remains the same. Then we just need to implement this value with cursor method. So let's do that. I'll have a public function value with cursor, and it accepts an int maxWidth and returns a string. Okay, so we can say if this chat currentMessage, if there's no currentMessage, if it's equal to a blank string, let's return a placeholder. So we can say this dim, this addCursors, we're going to add a placeholder fake cursor that says chat with Hal. And the cursorPosition is just going to be zero, it's going to be at the beginning, and
that says chat with Hal. And the cursor position is just going to be zero, it's going to be at the beginning, and we'll say maxWidth. Okay, so if we have an actual message available to us, we could say return this.addCursor(); We're going to say this.chatCurrentMessage.stringLength() of the current message. And we'll apply the maxWidth. So if we actually have a current message, add the cursor at the end of the current message so it looks like we're typing it out. Otherwise, add a placeholder and put the cursor at zero. That's all that does.
Fixing Columns and Alignment11:45
Otherwise, add a placeholder and put the cursor at zero. That's all that does. Okay, let's fix up our content rendering. So we have this in two columns. Right now we're just actually printing this out without any regard to the column, so let's actually fix that. So this is our left column. These are the lines for the left column. We need to set up our divider line, which is going to be equal to a collection of a range from 1 to our chatCount, so we want it to be the same height as the chat, and
We need to set up our dividerLine, which is going to be equal to a collection of a range from one to our chatCount, so we want it to be the same height as the chat, and we'll say map function this dim with this dividerLine. Okay, so what we have right now is lines for the left column, lines for the chat, lines for the dividerLine. Let's do our chewyHelper, lines from columns, and we have three columns, left column, dividerLine, chatLines, and we want the spacing to be equal to our columnSpacing, and we want to get those resulting lines, and we want to print those out. So we're basically creating a bunch of lines from columns, spacing them with our columnSpacing, getting the resulting lines, printing them out, and this should be chat, not chat.
So we're basically creating a bunch of lines from columns, spacing them with our column spacing, getting the resulting lines, printing them out, and this should be chat, not chat lines. This should do it. Let's see if it works. Okay, so we have typing, the shipHealth is going crazy, the powerShields and weapons are going crazy. This all looks pretty good. The only thing I don't like is that this isn't quite aligned, so let's fix that real quick. It's probably, I would say, two spaces off from being correct, and yep, how it comes
The only thing I don't like is that this isn't quite aligned, so let's fix that real quick. It's probably, I would say, two spaces off from being correct, and yep, how it comes out. Okay, great. Let's just fix that, and I think we're pretty much there. So let's go down to renderChat, and we will here, we will say map function line, and I think we'll just put two spaces here, and then return the line. Okay. I think that should do it. Let's see if that works.
I think that should do it. Let's see if that works. Okay, everything looks good. Let's see if it lines up a little better. Oh, so much better. This is great. We're coordinating a lot of animations, and we're outputting all of them in different ways very easily. It's all very manageable, it's all very compartmentalized, and it's working really well. We have a blinking light, we have a number that's going crazy, we have bars that are
It's all very manageable, it's all very compartmentalized, and it's working really well. We have a blinking light, we have a number that's going crazy, we have bars that are going crazy, we have a chat that's going crazy. This looks really good. Great job.
