Tuesday 30 May 2023

Remote Work Is A Moral Issue For Elon Musk

Elon Musk, tech entrepreneur and Twitter fratbro extraordinaire is at it again. Just last week, during an interview with CNBC, in addition to his usual spiel about productivity in the office, he opined the following, while putting on the most snicker-inducing look of put-upon indignation ever.

It's like, really, you're going to work from home and you're going to make everyone else who made your car come work in the factory? You're going to make the people who make your food… that they can't work from home? The people that fix your house - they can't work from home? But, you can? Does that seem morally right? That's messed up.

People should get off their goddamn moral high horse with the work-from-home bullshit. They're asking everyone else to not work from home while they do.

The laptop class are living in la-la land.


Make no mistake, there are parts I agree with. Remote work is not, and should not be, an entitlement. I would even be OK with his argument that people are more productive in the office, because there are some cases where that's true. But the rest of the argument? There's no way to be nice about it: framing it as a moral issue is jaw-droppingly stupid, devoid of any coherent logic whatsoever.

I feel like the flaws are evident enough that I shouldn't have to address them, but here we go anyway.

Dismantling the "moral" argument

Before we begin, I should just state that I do work remotely, with days spent in the office when it becomes absolutely necessary. However, it's more of a perk than any sort of entitlement. And while I may have the occasional day where I just can't seem to get much work done, I also have them in the office. Not working in Silicon Valley probably excludes me from the "laptop class" that Elon Musk is taking such pains to dump on, though.

I'm not sure what "make everyone else work in the factory" means. I have neither the authority nor wherewithal to make anyone do anything. If they have to work in the office, that is the nature of their job, and me working from home or office or planet Mars does nothing to change that. Thus, that part already is perplexing in its sheer absurdity.

Working from outer space.

As for "the people who make your food… that they can't work from home? The people that fix your house - they can't work from home? But, you can? Does that seem morally right? That's messed up.", let's try to justify that statement.

How about we say, "you're going to buy Twitter but you're gonna make the rest of us pay eight dollars to get a blue check on our accounts? We can't buy Twitter, but you can? Does that seem morally right? That's messed up."?

Did that make sense? No, it didn't make a lick of sense. Consequently, neither does his argument.

Lastly, morality is subjective. Trying to introduce a subjective component into a business decision (I assume that the issue of remote work is a business decision for people like Elon Musk, but you can never tell these days) is doomed to failure. Just because you feel that something is immoral doesn't make it immoral for the rest of the world. Guess who needs to get off their moral high horse?

He said what he said, but why?

Normally, if it had been anyone else who uttered this, the first thing I would have wondered was, how did this moron manage to tie his shoelaces in the morning, much less make it to a CNBC interview? But this is Elon Musk, yo. He is the CEO of SpaceX and Tesla, both huge enterprises. He's one of the world's richest men, and while wealth is no indication of IQ, let's just say that I highly doubt that the Elon Musk is an idiot of that magnitude.

In other words, there's no way he's that big a jackass. So let's get that out of the way.

The easiest (and probably laziest) explanation is that he was high when he made these comments. Gotta lay off the weed, bro.

A more charitable interpretation is that Elon Musk does truly have something against remote work and he let his emotions get the better of his logic. He wouldn't be the first, millionaire genius status notwithstanding.

Look at me!

The last, and unfortunately most believable explanation is that it doesn't matter what Elon Musk believes, he's going to say whatever gets him the most eyeballs. Years of watching Elon Musk make an absolute monkey of himself on Social Media has driven me to the conclusion that the man's a bit of an attention junkie.

I think he's playing to a certain gallery. It's extremely unlikely that people who work from home are going to listen to that largely performative rant and tearfully repent of their immoral ways, thus I can only speculate that he is courting the attention of Boomer Bosses very traditional-minded employers.

Finally

There are no absolutes where remote work is concerned. It's not inherently good or bad. It is merely one possible component in an employer-employee relationship. Tech workers work with software, therefore remote work is a distinct possibility. Not so much for others.

And no, I don't think even Elon Musk truly believes that it is a moral issue.

But whatever, guys. I'm done. An entire blogpost devoted to this, is already more attention from me than the man deserves. Points for effort, though. He certainly had me going.

Signing off from home,
T___T

Thursday 25 May 2023

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

The last episode, Hated by the Nation, is styled like a police procedural and reminds me strongly of movies like Se7en, with a tech bent. It also boasts a really large cast.

This is the quintessential Black Mirror in the sense that the evils of Social Media and cyberbullying is central to the plot. In fact, it's like a violent and explosive version of The National Anthem.

The Premise

It's a few years into the future, and the extinction of bees has given rise to a tech creation known as Autonomous Drone Insects (ADIs). These tiny robots are used as an instrument for murder, with Social Media as a trigger. A nasty twist at the end ensures that few remain unscathed, either physically or psychologically.

The Characters

Kelly McDonald as Karin Parke. She's a jaded and cynical veteran of the force, and a divorcee. Every sarcastic quip and world-weary expression McDonald throws out is gold. For real, the dialogue is excellent. And McDonald delivers like a pro.

Liza: What about the others who chipped in? Are you going to tell them off too? I didn't do anything!

Karin: Start a thread about it.


Now that was a performance I truly enjoyed. She puts a lot of people in their place just with a look or a carefully-placed stinging remark. Which makes it all the more poignant when it's quite obvious that the events have somewhat broken her.

Faye Marsay as Blue Coulson. A former idealist who wants some field experience. Coulson is tech-savvy and does a lot of the investigation regarding how the "Game of Consequences" works. She's a great foil to the bitter jadedness of Karin Parke.

Benedict Wong as Shaun Li from the National Crime Agency. I just about fanboyed out loud when the Sorceror Supreme appeared on screen! In here, he's a straight-laced, growly-voiced semi-antagonist, serious as a heart attack.

Jonas Karlsson plays the ADI engineer Rasmus Sjoberg. He provides, with earnestness, most of the exposition around the ecology of ADIs and the tech behind it.

Joe Armstrong as Nick Shelton. Other than being a victim, I'm not sure what use he was to the plot.

Elizabeth Berrington as Jo Powers. She's a provocative journalist who writes vicious articles that punch down, and punch down hard. This makes her a publicly hated figure, and she's the first victim of the hashtag. Berrington plays her as a woman with an extremely thick skin who weathers the constant vitriol hurled her way, letting everything roll off her.

Charles Babaloa as Tusk. An obnoxious black rapper who becomes the next target of public hate and thus dies a grisly death.

Ben Miles makes a delightfully foul-mouthed appearance as Tom Pickering, the slimy and heartless politician. We see him burst into expletives under pressure, and it is so fun to watch.

Holli Dempsey as Clara Meades. She's presented as a feckless young woman who earns public hate due to a silly and disrespectful prank.

James Larkin as Simon Powers, Jo's husband. Now, this role is tragic. Larkin plays him as a traumatized husband who had to watch his wife kill herself in violently crazed fashion.

Georgina Rich as Tess Wallander. She's a soft-spoken individual who was a victim of cyber-bullying, and the original inspiration for the deadly hashtag.

Duncan Pow as Garett Scholes. Some psycho who dispenses what he calls justice... but with the tech skills to actually make it happen. We don't see much of him except toward the end, and even then there's not all that much of him to form a strong opinion.

Vinette Robinson as Liza Bahar. She's a pre-school teacher who is found to be using the hashtag without really understanding the consequences but otherwise does not seem to be a horrible person. Later on, Scholes delivers her comeuppance and I can't help but feel that perhaps it was a bit much. It was probably deliberate, to show us just how disproportionate extremism can be.

Esther Hall as Vanessa Dahl. What was she even doing taking up space on-screen? What a waste. She existed for no other reason than to be the verbal punching bag for other characters, mostly Karin.


The Mood

It's a big city vibe, but with grim undertones to it. Karin and Blue pretty much nail it with their conversations while driving. The surroundings are bustling and lively and the investigative feel of the entire episode is pretty pervasive.

What I liked

The red herring in the form of Jo's cake was clever. And yes, like her husband said, "very creative".


The scene where Karin and Blue initially have a conversation in the former's car, contains a reference to the Series Two episode, White Bear. And it carries the same vibe that Brad Pitt and Morgan Freeman had in Se7en, with Kelly McDonald playing the part of the jaded veteran, and Faye Marsay being the earnest newbie who just wants to make the world a safer place.

I thought Tusk's death was particularly creative. Gotta see it to appreciate it.


I like the display where several ADIs fly in formation to form Granular's logo. It even rotates!


The screenshot doesn't do it justice, but the interior of Shaun's NCA-issued car is really impressive!


That sequence of the ADIs leaving their hive to wreak bloody murder, is both poetic and terrifying.

That entire episode, along with the final twist, is very characteristic of Black Mirror. The horrors of Social Media, and how people are utter dicks to each other. In particular, how people don't stop using the hashtag even after finding out what it does, and actually pile it on. This makes it really hard to sympathize with them later on.


And later on, when Karin and Blue wander through that hall filled with bodybags of victims, the sheer bleakness of the shot is iconic.

What I didn't

The format of having the episode framed by Karin as a narrator, felt unnecessary. The ending was also pretty ambiguous. This episode could have worked better as a full-length movie, with perhaps a proper resolution. As it was, it was already about 90 minutes in length.

Wait, if the ADIs attack based on facial recognition, couldn't potential victims just mask their faces for protection? Isn't that exactly what Scholes does at the end? Why the need for that sequence regarding the bunker to protect Pickering? This seems like quite the plot hole.

Conclusion

This final episode really rounded up Black Mirror Series Three really well. While the cast just seemed a little larger than it really needed to be, the snappy dialogue and tension-laced action absolutely did it for me. This is a great addition into the franchise despite some totally superfluous bits.

My Rating

9 / 10

Final Thoughts on Black Mirror Series Three

I liked Series Three as a whole. After the slight disappointment that was Series Two, Black Mirror came back strong with plenty of content, all the while staying somewhat true to what came before.

San Junipero was the standout, though in terms of storyline and vibe, it was very different from the entire Black Mirror universe. Still, it was a sweet love story that strongly resonated.

A close second was Nosedive and Shut Up and Dance. These were episodes that were very typical of Black Mirror, showcasing the ugliness of humanity, amplified by advanced (or in the case of Shut Up and Dance, not all that advanced) technology. Men Against Fire and Hated in the Nation were both strong action-packed offerings and gave me a satisfying conclusion to Series Three. And while Playtest wasn't (in my opinion) as good as the others, it was by no means a weak episode.

All in all, this was very, very satisfying.

#DeathToBlackMirror (heh heh)
T___T

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