Tuesday, 23 May 2023

Film Review: Black Mirror Series Three, Redux (Part 2/3)

This next episode is Men Against Fire, and deals with what looks like a post-apocalyptic world where the same Zed-eyes technology is deployed. This time, it's known as the MASS system and controls what soldiers see, hear and smell. This episode is actually very heavy on the action, though dread also plays a big part. The creep factor comes in midway through this episode.

The Premise

This episode centers around a soldier, named Stripe, who is initially tasked as part of a team effort, to eliminate a race of diseased humans named "Roaches". Things take a sinister turn when he discovers the truth behind the Roaches and has to live with his conscience.

The Characters

A very buff and good-looking Malachi Kirby plays Stripe, real name Koinage. He does OK as a soldier starting off enthusiastic and compassionate, and later on transitioning to a human being wracked by guilt. I didn't feel all that strongly about his performance because he kind of gets outacted by almost everyone he has a scene with.

Madeline Brewer knocks it out of the park in the role of Raiman. She's a soldier like Stripe, all tomboy bloodthirsty action-girl and cute as a button. The boisterous performance she puts up here, along with lewd commentary and good-natured roasting, it's really easy to see her as one of the guys despite her pint-sized figure. This is pretty much a supporting role, but it's done so well.

Michael Kelly as Arquette. If there's any way to instantly point to a character as the real bad guy, it's to cast the man who played Douglas in House of Cards, in this role. Kelly's dead-eyed gaze and soft-spoken affability make him a terrifying bad guy and utterly believable sociopath.

Sarah Snook as Medina, Stripe and Raiman's commanding officer. The script requires her to be matter-of-fact, dignified and steely, which she does really well. Up until her sudden death.

Ariane Labed cuts a tragic figure as Catarina. She delivers the much-needed exposition before her death at Raiman's hands.

Loreece Harrison as Stripe's fantasy girl. That was the source of the sex and nudity in this episode, but she's not just a pretty face. The smile on that face is sometimes subtly unsettling, and I'm not sure if that is deliberate. Kudos if so!

Kola Bokinni as Lennard. This guy is surly and mean. And that's all we actually see of him, which is a pity because so much more could've been done.

Francis Magee as Parn Heidekker. Magee plays him with solemn gravitas.

Simon Connolly as doctor. Just a bit part which felt totally unnecessary.

The Mood

In contrast with the previous episode, this episode is almost colorless. It's very drab, with everything viewed in very bleak hues. It paints the picture of a world with no life in it.



Even in the end sequence where we see Stripe's actual home against what he is seeing, in the "idealized" version, the most we get is pastel-white.

Definitely there's a whole lot of blood and violence in here and it feels like an action flick.

What I liked

The translator gadget is a nice touch.


The MASS technology allows images to be transmitted to the brains of the soldiers. This is a very cool, and important plot point. 3D-imaging too! Great for tactical decisions.


That scene where Stripe's dream sequence with his dream woman turns into a full-on orgy with several identical girls is both erotic and extremely creepy.

Medina's sudden death was a shock to me. Well played!

I like how Stripe's senses come back once his MASS implant is damaged, being able to smell grass and all. It's good foreshadowing on how the MASS system was obscuring the truth.

Arquette's entire conversation with Stripe at the end. Michael Kelly is in his element here. All that exposition and histrical background, leading up to the sadistic choice he gives Stripe. This was both a spine-chilling and awesome sequence.

What I didn't

The title of this episode doesn't seem to make any sense. What does "Men Against Fire" mean, exactly?

The faces of the Roaches just look like cheap plastic masks. Granted, this is probably budget constraints, but still.


Raiman sings that song How do I know what love is to annoy Parn Heidekker and ends up annoying me as well. Like, do we always have to hear this song? This is the third time it's appeared in a Black Mirror episode. The first time was nice, the second was amusing, but enough is enough.

The entire thing just felt a bit light in the story department. It was more of a creepy action movie than an actual Black Mirror episode.

Conclusion

Phew! This was one brutal episode. It's unflinching in its depiction of violence, showing us that women and children get gunned down, with the accompanying blood and guts. There's racial purity fanaticism at the core of this, and an examination of how augmented reality and propaganda can further some truly horrific causes. Remember, the villagers in the story don't have the excuse that they literally see Roaches as monsters.

Special shout-out to Michael Kelly. He really made this episode for me. What an actor. He just played a heinous psycho so well.

My Rating

8.5 / 10

Next

Hated by the Nation

Sunday, 21 May 2023

Film Review: Black Mirror Series Three, Redux (Part 1/3)

We're back with the next three episodes of Black Mirror Series Three! Took me a few weeks (or several), but here it is. We will be going through San Junipero, Men Against Fire and Hated in the Nation. There are a few surprises in store.

Warning

There will be spoilers ahead. And plenty of profanity. And stories of tech fouling up our lives. Basically, whatever you've come to expect of Black Mirror, is gonna be in there, and more!

The Premise

This next story, San Junipero, takes place largely in a virtual reality world, where the time periods vary. It's also pretty much a love story and there's even a happy ending. But it wouldn't be Black Mirror if there wasn't a little something at the end...

The Characters

Mackenzie Davis is the lanky and timid Yorkie, who learns to live even as she dies. I last saw Davis in Terminator: Dark Fate. I didn't love the movie, but this woman's one hell of an actress! These two characters couldn't be more different.

Gugu Mbatha-Raw is Kelly. Vivacious, bodacious. And those bedroom eyes! And that temper when its let out. This lady delivered.

Gavin Stenhouse as Wes, whom Kelly describes as "not a bad guy" despite being all obsessed after their one-night stand. Stenhouse plays him as a somewhat decent guy who has his own issues to work through.

Denise Burse as actual Kelly. She looks like she could actually be an aged Kelly. Burse plays her with grace and just the right amount of cheekiness.

Annabel Davis as actual Yorkie. There doesn't seem to be much to the role; they could have just used a mannequin.

Raymond McAnally as Greg, Kelly's nurse. He's played as this jovial nice guy with an "aw shucks" demeanor. Not gonna lie, I snickered a little at the actor's name.

Billy Griffin Jr puts in a couple appearances as video game nerd and romantic hopeful Davis. There's something very endearing about his puppy-dog enthusiasm.

The Mood

It begins in colorful fashion as the episode tells us that this is the 80s with references to Max Headroom, The Lost Boys and Belinda Carlisle. It's a very party vibe complete with big 80s hair, disco and even arcade games. Later time periods are less flashy, showcasing movies like Scream, The Bourne Identity and even what looks like a Pink music video.

Soon, we're taken on a tour of the seaside town that is San Junipero and things get rather strange from there. And all around, there's always a hint of something else going on beneath the surface, like nothing is what it seems.

This also seems to be the first episode in Black Mirror that's set in the USA, rather than in the UK.

What I liked

The little subtle nods to what was really going on. Yorkie turns down a chance to play Top Speed when she sees the animated car crash because it reminds her of her own accident. The club is named Tucker's, and the company that actually sets up the simulation, is TCKR. Even the name "San Junipero" is a Spanish version of the hospital's name, "St Juniper's".

The music! The 80s fashion! The 90s visuals! So good. Even the arcade games change with the time priod. Love it!

The Quagmire is awesome in its depravity and I'm really hoping it gets more airtime in future episodes.

The lesbian sex between Kelly and Yorkie is kept brief and just long enough to be obvious as to what's going on. It's not played for titillation, and the pillow talk is both tasteful and contains clues as to what San Junipero really is.


That scene where Kelly breaks the mirror and it magically repairs itself, is such a sly nod to the series.


That futuristic vehicle that Kelly rides in... that design. It speaks to me.


Yorkie's vanity plate is cute.


That slightly creepy vibe in the mid-credits. This is Black Mirror, remember?

What I didn't

I was slightly disappointed to find that there's no nasty ending to this episode. Yes, Black Mirror has conditioned me to expect certain things.

That close-up of Kelly's gravestone was nowhere clear enough for me to see the name. Minor quibble, I know.

Conclusion

This was the only Black Mirror episode so far that wasn't depressing as fuck. Normally, I'm not a fan of love stories, but this one was surprisingly poignant. It dealt with existential issues without going in way too deep, and touched me on a very emotional level.

My Rating

9.5 / 10

Next

Men Against Fire

Wednesday, 17 May 2023

Techronyms and their place in the industry

Tech terms tend to be long, cumbersome and confusing, until we compact them into clever acronyms. The tech industry is full of these, and it's generally important for tech workers to be familiar with them. It allows us to communicate better without resorting to word salad. It keeps communication terse.

I like to call them "techronyms".

Using those letters!

Many years ago, I had a boss who was dismayed at how some members of our team did not even know what "HTTP" stood for, and made it a point to test us by playing a game. He would toss out acronyms at us while we told him what each one stood for. While the game was mildly amusing, it also struck me as ultimately pointless.

Here's why

I've come across people who throw out acronym after acronym in an attempt to sound savvy. These tech poseurs get away with it precisely because they are throwing them at laypeople who just lap it up.

Pretty, but empty.

At the end of the day, it's empty knowledge. It's just used by people to appear knowledgeable without actually having to expend the effort to acquire that knowledge. All the while, people who actually have that knowledge aren't heard because they don't look like they know anything.

By placing this much emphasis on knowing acronyms, we enable this behavior.

What really matters

Is it important that web developers know what "HTTP" stands for rather than simply knowing that it's the prefix of a generic URL? (That's Universal Resource Locator, by the way. Heh heh)

Sure it is. But is it enough?

So imagine if we knew that "HTTP" was an acronym for "Hyper-text Transfer Protocol" but couldn't explain what that meant? Wouldn't it amount to the same thing?

Learn the concepts, not
just the acronyms.

Maybe it's just me, but I would rather tech workers understand the concepts rather than simply being able to parrot the terms. If a tech worker could tell me that the protocol is a set of procedures used to transport HTML content from server to client, I really wouldn't care if this person knew exactly what it stood for. Let's be honest here; what knowledge is more valuable - the label or the concept? Ideally, we'd know both, but if we could only choose one, I know what I would choose.

That's all!

That was just a short rant. Needed to get it out there.

Ultimately, it doesn't matter. Fakers are gonna fake. And the people who actually know their shit, will just go on being useful. The world will continue to spin on its axis, and if such is the world we inhabit, I'll make my peace with it.

TTYL,
T___T

Wednesday, 10 May 2023

Sperminator: A TeochewThunder Project

Yet another project is complete!

Years ago, back in 2015, I embarked on a learning project to experiment with mobile technology. Halfway through my journey, I began toying with Android's built-in gyrometer. This resulted in me making a simple compass app.

Hey, don't laugh. We all gotta start somewhere, right?

Well after that was done, I had another thought. What if I made a scrolling game where I had to evade obstacles by tilting the phone? What if the game landscape was a birth canal and the player was a tiny sperm wriggling across it?

The idea was corny. Silly. Utterly ridiculous. And so me.

Thus, the first version of Sperminator was born.

Sperminator v 1.0

At the time, I was still deeply mired in classic HTML, CSS and JavaScript. I used Chinese to provide instructions and text, to take up less screen space. I created all manner of stage bosses, including one that looked like Captain Jack Sparrow. In fact, the player sprite itself looked like Captain America!



Every animation sprite was basically CSS3, painfully battered into submission. I ran into all sorts of code design problems because I was basically coding on the fly, with no thought to scope. My naming conventions were horrible. That was where I was as a JavaScript coder. I was more concerned about getting it to work, than about proper practices.

For months, I slaved over my vision. And finally it was done.


I exported it to the Android platform, and got people to play it. For months, I had test and feedback groups in WhatsApp. I even got a kick out of having people play it and telling them, completely tongue-in-cheek, to "eat the sperm". When I went for job interviews, this was what I exhibited to prove I could code.

Yeah, I sure got a whole lot of mileage out of that one.

Months went by. A year. My old phone died, and as I discovered new technologies to play with, whatever meager skill I'd picked up with Android deployment, atrophied. I was having career problems, what with the companies I was in, shutting down and all. Suffice to say, I had bigger fish to fry. Much bigger. The existential kind.

And thus my little foray into writing an Android game, experienced death via atrophy.

Sperminator v 2.0

By the time I got back to this in 2020 or so, I had picked up new things. My JavaScript was arguably better. I was ready to give this a second go.

The first thing I did was scrap the CSS animation, and instead make everything SVG based. This gave me a whole lot more leeway to add details to the graphics I had not been able to previously without a whole lot of pain.





My JavaScript was more structured. I put things in objects, with methods, and called them without running into scope problems. To save development time, I used jQuery.


One of the biggest and most obvious things I changed, from a technical perspective, was that I no longer bothered about exporting it to Android. I let it play on the browser. My wife (I was married by that point), helped me take videos of the on-screen animation while I played the game. I don't think she thought much of my work, to be honest, but she was probably glad that these are the nerdy things I occupy myself with.

And in 2022, after some cleanup, this is what we have. It not only looks better, it moves better and I have little to no cross-browser issues.




The gameplay was improved as well. I reproduced the rules from Version 1.


Life: Represented by a red bar. It is reduced by bumping into bacteria. If it is reduced to 0, Sperminator is dead and the game is over. It can be replenished by grabbing Cross icons.
Energy: Represented by an orange bar. Going into Turbo mode (moving fast) and bumping into other sperm reduces it. If it is reduced to 0, Turbo mode is no longer possible. It is replenished by grabbing Lighting Bolt icons.
Star Power: Represented by a yellow bar. Constantly goes down. Once it is full, Sperminator gains the ability to replenish Life by "eating" other sperm. It is replenished by grabbing Star icons.
Shield: Represented by a blue bar. Once a Shield icon is grabbed, the bar is full and starts draining. Until it reaches 0, Sperminator is immune to bacteria and other sperm.



And the bosses! I went for a spiky-haired punk, the Jack Sparrow one from Version 1, and a bad bitch. I think the dialogue was improved too.




Epilogue

Sometimes it's rewarding to revisit a project and make improvements. In addition to discovering what you were capable of despite limited knowledge, you get to add on to what you did previously, now armed with both experience and more knowledge. The stark difference between Version 1 and Version 2 of Sperminator, is testament to this.

Hope it went swimmingly for you,
T___T

Thursday, 4 May 2023

Web Tutorial: QBasic Filestitch

It's exceedingly rare that I ever get to use QBasic to solve a work problem. As such, when that happens, I'm inclined to commemorate it in true geek fashion - by writing a web tutorial.

Some context coming up!

When my company last year upgraded to Oracle Cloud, all the POS machines at various outlets were now sending CSV files to the relevant department. The problem was, whereas before the files were already consolidated before the department received it, with this new, ahem, upgraded system, the outlets were now sending the files separately. All twenty of them. As you can imagine, manually stitching the files together daily, quickly became a total pain in the ass.

What I eventually did was use QBasic to write an executable file that would perform the consolidation for them. All they needed to do was double-click on the executable, fill in a bit of information, and an entirely new CSV file would be produced in seconds, or less!

Today, I will show you how to write this code. Of course, because I don't want to give away any company secrets, certain things have been changed. But the idea remains the same!

We first have a series of CSV files. You can access them at my repository. Each of these files are named this way.

tx_{date in "YYYYMMDD" format}_{outlet four-digit id}.csv.

These have the following fields. A sample is shown here. The fields are for date, time, order number, item name, category, quantity and order type.

filestitch/tx_20221210_1122.csv
"2022-12-10", "11:16:12", "100001210015", "Strawberry Deluxe Surprise", "MAIN", "1", "DINE-IN"
"2022-12-10", "11:16:12", "100001210015", "Mango Slurpee", "DRINK", "2", "DINE-IN"
"2022-12-10", "11:18:09", "100001210016", "Chocolate Overload", "MAIN", "2", "TAKE-AWAY"
...


filestitch/tx_20221210_1123.csv
"2022-12-10","10:45:36","444002770055","Mixed Fruit Cheesecake","SIDE","1","DINE-IN"
"2022-12-10","10:45:36","444002770055","Hot Six","MAIN","1","DINE-IN"
"2022-12-10","10:45:36","444002770055","Chocolate Overload","MAIN","1","DINE-IN"
...


filestitch/tx_20221210_1124.csv
"2022-12-10","11:00:02","300005557800","Banana Sandwich Special","MAIN","4","TAKE-AWAY"
"2022-12-10","11:02:58","300005557801","Cream Cake","SIDE","1","TAKE-AWAY"
"2022-12-10","11:03:55","300005557802","Ice Lychee Tea","DRINK","2","TAKE-AWAY"
...


filestitch/tx_20221210_1125.csv
"2022-12-10","10:49:10","200000778889","Blueberry Muffin","SIDE","4","TAKE-AWAY"
"2022-12-10","10:55:11","200000778890","Blueberry Muffin","SIDE","2","TAKE-AWAY"
"2022-12-10","11:20:06","200000778891","Chocolate Overload","MAIN","1","TAKE-AWAY"
...


There are only four files, but in the case of my company, we had to deal with over twenty files. This is how we define each outlet, through the ids and outlets arrays.
CLS

DIM ids(4) AS STRING
DIM outlets(4) AS STRING

ids(0) = "1122"
outlets(0) = "TUAS"
ids(1) = "1123"
outlets(1) = "PASIR RIS"
ids(2) = "1124"
outlets(2) = "BALESTIER"
ids(3) = "1125"
outlets(3) = "LITTLE INDIA"


Here, we ask for the date, and ask for it in a certain format. We assign it to the variable FileDate. This is going to help us identify the correct file.
ids(3) = "1125"
outlets(3) = "LITTLE INDIA"

INPUT "Date of file? (YYYYMMDD format, eg, 20221101)", FileDate$


And then we ask for the directory, and assign it to the variable FileDir. Here, we're going to assume it's in the C drive.
INPUT "Date of file? (YYYYMMDD format, eg, 20221101)", FileDate$
INPUT "Directory?", FileDir$


This is pretty basic (hur hur) so far.




And here, we create a file in the directory specified by FileDir, with the name derived from FileDate. This will be the CSV file we write to. We will define this as #1.
OPEN "C:\" + FileDir$ + "\tx_" + FileDate$ + "_consolidated.csv" FOR OUTPUT AS #1


What we have now is a For loop to iterate through the outlets. As far as I know, there is no graceful way to grab the size of an array in QBasic, so let's just hardcode this. And after everything, we close #1 as a matter of good cleanup practices.
OPEN "C:\" + FileDir$ + "\tx_" + FileDate$ + "_consolidated.csv" FOR OUTPUT AS #1

FOR i = 0 TO 3 STEP 1

NEXT

CLOSE #1


We formulate a filename based on the element in the ids array pointed to by i, FileDate and FileDir. Needless to say, that directory actually has to exist. On the next line, we print FileName for better visibility.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$

NEXT


Here, we open the file. It's defined as #2.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
NEXT


Now, what if that file isn't found or is empty? We trap this condition in an If block, checking if #2 has no data. We use the LOF() function, passing in 2 (for #2)as an argument for this. If it returns 0, either no such file exists or the file is empty.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN

    ELSE

    END IF

NEXT


And if so, we print out the name of the outlet (using the element in the outlets array pointed to by i) and use the KILL statement on #2. I know this sounds really violent, but all it does is delete the file, if found.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE

    END IF
NEXT


If there is such a file and it has data, we use a Do-while loop, ending only when the end of the file is reached. This is checked using the EOF() function and passing in 2 (for #2) as an argument.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE
        DO WHILE NOT EOF(2)

        LOOP
        CLOSE #2

    END IF
NEXT


So let's try it with an obviously wrong date and directory. You'll get a few of these, two for each file. Just keep clicking "Yes".




Here, it lists all the files it didn't find, which is all four of them.




Here, we use the INPUT statement to get the data from #2, the current file! After the first argument, it's all the data from the CSV files, separated by commas. Note that it has to be, for obvious reasons, in the same sequence!
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE
        DO WHILE NOT EOF(2)
            INPUT #2, COLDATE$, COLTIME$, COLNUM$, COLITEM$, COLCAT$, COLQTY$, COLTYPE$
        LOOP
        CLOSE #2
    END IF
NEXT


Then we have InputStr, which is initialized as an empty string. Then we append all the data to it. Note that we have to use the CHR$() function with 34 as an argument, to add the double-quotes.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE
        DO WHILE NOT EOF(2)
            INPUT #2, COLDATE$, COLTIME$, COLNUM$, COLITEM$, COLCAT$, COLQTY$, COLTYPE$
            InputStr$ = ""
            InputStr$ = InputStr$ + CHR$(34) + COLDATE$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTIME$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLNUM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLITEM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLCAT$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLQTY$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTYPE$ + CHR$(34) + ","

        LOOP
        CLOSE #2
    END IF
NEXT


We also add this line to write the outlet name into a new column.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE
        DO WHILE NOT EOF(2)
            INPUT #2, COLDATE$, COLTIME$, COLNUM$, COLITEM$, COLCAT$, COLQTY$, COLTYPE$
            InputStr$ = ""
            InputStr$ = InputStr$ + CHR$(34) + COLDATE$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTIME$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLNUM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLITEM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLCAT$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLQTY$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTYPE$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + outlets(i) + CHR$(34)
        LOOP
        CLOSE #2
    END IF
NEXT


Finally, we write InputStr into #1.
FOR i = 0 TO 3 STEP 1
    FileName$ = "C:\" + FileDir$ + "\tx_" + FileDate$ + "_" + ids(i) + ".csv"
    PRINT FileName$
    OPEN FileName$ FOR INPUT AS #2
    IF LOF(2) = 0 THEN
        PRINT outlets(i) + " OUTLET HAS NO DATA"
        KILL FileName$
    ELSE
        DO WHILE NOT EOF(2)
            INPUT #2, COLDATE$, COLTIME$, COLNUM$, COLITEM$, COLCAT$, COLQTY$, COLTYPE$
            InputStr$ = ""
            InputStr$ = InputStr$ + CHR$(34) + COLDATE$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTIME$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLNUM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLITEM$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLCAT$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLQTY$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + COLTYPE$ + CHR$(34) + ","
            InputStr$ = InputStr$ + CHR$(34) + outlets(i) + CHR$(34)
            PRINT #1, InputStr$
        LOOP
        CLOSE #2
    END IF
NEXT


Run this! This time, use "20221012" and "filestitch" as the inputs, to fit the file names.




And it's done.




A new file, tx_20221210_consolidated.csv, should appear in the directory. Its contents should be the combination of all the other files that have "20221210" in the filename. And at the end of each line, we have appended the outlet name!
...
"2022-12-10","15:21:02","100001210027","Strawberry Deluxe Surprise","MAIN","1","TAKE-AWAY","TUAS"
"2022-12-10","16:16:08","100001210028","Mixed Fruit Cheesecake","SIDE","1","TAKE-AWAY","TUAS"
"2022-12-10","17:09:14","100001210029","Mixed Fruit Cheesecake","SIDE","1","TAKE-AWAY","TUAS"
"2022-12-10","18:55:55","100001210030","Blueberry Muffin","SIDE","12","TAKE-AWAY","TUAS"
"2022-12-10","19:21:41","100001210031","Over-the-top Meltdown","MAIN","1","TAKE-AWAY","TUAS"
"2022-12-10","19:44:30","100001210032","Banana Sandwich Special","MAIN","10","TAKE-AWAY","TUAS"
"2022-12-10","19:51:02","100001210033","Strawberry Deluxe Surprise","MAIN","5","TAKE-AWAY","TUAS"
"2022-12-10","19:51:02","100001210033","Blueberry Muffin","SIDE","5","TAKE-AWAY","TUAS"
"2022-12-10","19:52:11","100001210034","Chocolate Cat And Mouse","MAIN","1","TAKE-AWAY","TUAS"
"2022-12-10","19:53:41","100001210035","Over-the-top Meltdown","MAIN","1","DINE-IN","TUAS"
"2022-12-10","19:53:41","100001210035","Chocolate Overload","MAIN","1","DINE-IN","TUAS"
"2022-12-10","19:55:58","100001210036","Cream Cake","SIDE","4","TAKE-AWAY","TUAS"
"2022-12-10","19:56:55","100001210037","Fruit Fritters","SIDE","10","TAKE-AWAY","TUAS"
"2022-12-10","19:58:10","100001210038","Strawberry Deluxe Surprise","MAIN","1","DINE-IN","TUAS"
"2022-12-10","19:58:10","100001210038","Hot Six","MAIN","1","DINE-IN","TUAS"
"2022-12-10","19:58:10","100001210038","Blueberry Muffin","SIDE","5","DINE-IN","TUAS"
"2022-12-10","19:58:10","100001210038","Cream Cake","SIDE","2","DINE-IN","TUAS"
"2022-12-10","19:58:10","100001210038","Ice Lychee Tea","DRINK","10","DINE-IN","TUAS"
"2022-12-10","19:58:10","100001210038","Ice Lemon Tea","DRINK","2","DINE-IN","TUAS"
"2022-12-10","10:45:36","444002770055","Mixed Fruit Cheesecake","SIDE","1","DINE-IN","PASIR RIS"
"2022-12-10","10:45:36","444002770055","Hot Six","MAIN","1","DINE-IN","PASIR RIS"
"2022-12-10","10:45:36","444002770055","Chocolate Overload","MAIN","1","DINE-IN","PASIR RIS"
"2022-12-10","10:58:09","444002770056","Over-the-top Meltdown","MAIN","2","TAKE-AWAY","PASIR RIS"
"2022-12-10","10:58:09","444002770056","Passionfruit Excitement","MAIN","2","TAKE-AWAY","PASIR RIS"
"2022-12-10","10:58:09","444002770056","Mixed Fruit Cheesecake","SIDE","6","TAKE-AWAY","PASIR RIS"
...


Happy programming!

It's not often I get to use QBasic professionally, much less write an entire web tutorial for it. I thoroughly enjoyed myself here.

By fair means or file,
T___T