Thursday, May 25, 2023

Exploring Monster Taming Mechanics In Final Fantasy XIII-2: Relational Data

In this next installment of the miniseries of exploring the monster taming mechanics of Final Fantasy XIII-2, we'll fill out another database table that we need in order to start connecting all of the monster data together. In the last article, we built the core monster table with hundreds of attributes for each of 164 monsters. In the first article, we had identified four other tables that we would need as well, these being abilities, game areas, monster materials, and monster characteristics. The data in these four tables is all related in one way or another to the monsters in the monster table. We'll start with the abilities table, which will end up being three tables because we actually have passive, command, and role abilities. Once the passive abilities table is complete, we'll see how to connect that data in the database so that we can later make inferences on the data.

Final Fantasy XIII-2 battle scene

Finding Monster Abilities

Like for the core monster data, the first thing we need to do is get the data for the monster abilities into a .csv format. We'll start with the passive abilities because this is the largest group of abilities and they have a rank attribute that the role and hidden abilities don't have. Since there are over 200 passive abilities, we're going to want to pull this data out of the FAQ with another script instead of building it up manually. We can use an FSM very similar to the one used for parsing the monster data with a few simplifications because there are not nearly so many attributes for the passive abilities. I'll just throw the script out there, and then we can discuss it:

SECTION_TAG = "PassADe" ABILITY_SEPARATOR = "........................................" ABILITY_REGEX = /(\w\S+(?:\s[^\s\(]+)*)\s\((RL|\d)\)-*:\s(\w\S+(?:\s\S+)*)/ ABILITY_EXT_REGEX = /^\s+(\S+(?:\s\S+)*)/  end_abilities = lambda do |line, data|   return end_abilities, data end  new_ability = lambda do |line, data|   props = line.scan(ABILITY_REGEX)   if props.empty?     if line.include? ABILITY_SEPARATOR       return end_abilities, data     else       extra_line = ABILITY_EXT_REGEX.match(line)       data.last["description"] += ' ' + extra_line[1]       return new_ability, data     end   end    if props.first[1] == "RL"     props.first[1] = "99"     props.first[0] += " (RL)"   end   data << {"name" => props.first[0], "rank" => props.first[1], "description" => props.first[2]}   return new_ability, data end  find_abilities = lambda do |line, data|   if line.include? ABILITY_SEPARATOR     return new_ability, data   end   return find_abilities, data end  find_sub_section = lambda do |line, data|   if line.include? SECTION_TAG     return find_abilities, data   end   return find_sub_section, data end  section_tag_found = lambda do |line, data|   if line.include? SECTION_TAG     return find_sub_section, data   end   return section_tag_found, data end  start = lambda do |line, data|   if line.include? SECTION_TAG     return section_tag_found, data   end   return start, data end  next_state = start data = [] File.foreach("ffiii2_monster_taming_faq.txt") do |line|   next_state, data = next_state.(line, data) end
Starting from the bottom, we can see that we kickoff the FSM by looking for the SECTION_TAG, which is "PassADe" in this case. We have to find it three times instead of the two times we looked for the section tag in the monster FSM because the tag appears one extra time in the FAQ in a sub-contents under the main "Monster Infusion" section. Then, we look for the ABILITY_SEPARATOR, which is the same as the MONSTER_SEPARATOR from the previous script, and then go into a loop of matching and storing the ability data in the data list of hash tables. The ability data has a pretty clean format with the possibility of overflowing to a second line. The first couple abilities showcase the variations we have to handle:
Uncapped Damage (RL)---------: Raises the cap on damage to 999,999. Enhanced Commando (RL)-------: Enhances the Commando Role Bonus by one Bonus                                Boost.
We have a name that can be multiple words (and by looking further ahead we also see some special characters) followed by a space and a rank in parentheses, a -: separator, and the description, possibly extending to a second line. We want to capture the name, rank, and description, and the rank can be either "RL" (for Red Lock, meaning it can never be removed from the monster) or a number 1-9. From what we've learned of regexes in past posts, it should be clear that the regex

/(\w\S+(?:\s[^\s\(]+)*)\s\((RL|\d)\)-*:\s(\w\S+(?:\s\S+)*)/

is sufficient to match on the first line and capture those three attributes. Looking at the rest of the new_ability state code, we can see that if the line doesn't match the ABILITY_REGEX, it checks if the line is the ABILITY_SEPARATOR and moves to the last state if it is. Otherwise, we know that the line is an extra line of description, so we capture it and add it to the description of the last ability captured. If the line did match the ABILITY_REGEX, then we create a new hash with the values populated from the regex captures and continue in the same state.

Note that if the rank was "RL", we convert that to "99" so that all ranks will be integers in the database. Why not "10?" Well, when an ability is yellow-locked, its rank increases by nine, so making the red-locked abilities have a rank of 10 would conflict. I could have made it 19 or 20, but 99 works just as well and really sets them apart. We also need to add the " (RL)" text back into the name of the ability because it's possible to have red-locked and non-red-locked abilities with the same name, and they are different abilities for all intents and purposes.

Validating and Exporting Monter Abilities

Next, we want to do a little validation check on this data and write it to a .csv file, so here's the code to do that:
PROPER_NAME_REGEX = /^\w.*[\w)!%]$/ NUMBER_REGEX = /^\d\d?$/ FREE_TEXT_REGEX = /^\S+(?:\s\S+)*$/  VALID_ABILITY = {   "name" => PROPER_NAME_REGEX,   "rank" => NUMBER_REGEX,   "description" => FREE_TEXT_REGEX, }  data.each do |ability|   VALID_ABILITY.each do |key, regex|     if ability.key?(key)       unless ability[key] =~ regex         puts "Monster ability #{ability["name"]} has invalid property #{key}: #{ability[key]}."       end     else       puts "Monster ability #{ability["name"]} has missing property #{key}."     end   end end  require 'csv' opts = {headers: data.first.keys, write_headers: true} CSV.open("monster_abilities.csv", "wb", opts) do |csv|   data.each { |hash| csv << hash } end
Since we're building up each hash table directly with name, rank, and description, it's not necessary to make sure that each hash contains all three attributes and no others. We just want to make sure that each attribute value has the right (admittedly loose) format. Then we write it out to the monster_abilities.csv file.

Importing Monster Abilities

Moving right along, we can now import this .csv file into the database with another simple addition to our seed script:
csv_file_path = 'db/monster_abilities.csv'  CSV.foreach(csv_file_path, {headers: true}) do |row|   Ability.create!(row.to_hash)   puts "#{row['name']} added!" end
Before running this script, we need to generate the Ability model and its schema. Since there are only three attributes, we can easily create the migration script in one shot:
$ rails generate model Ability name:string rank:integer description:string
This command will create this migration file:
class CreateAbilities < ActiveRecord::Migration[6.0]   def change     create_table :abilities do |t|       t.string :name       t.integer :rank       t.string :description        t.timestamps     end   end end
Now we can run the migration and then seed the database:
$ rails db:migrate $ rails db:seed
And we've added our second table to the database with 147 abilities. (Wait, I thought there were over 200 abilities. Good catch. We'll get to that in a bit.) However, we should have a link between this new ability table and the monster table because every monster's many abilities should all be one of the abilities in the ability table.

Associating Monsters' Abilities with the Abilities Table

What we want to do in order to associate each monster's abilities with the abilities table is make each ability in the monster table a reference instead of a string, and point those references at the correct specific abilities in the abilities table. To accomplish this association, we'll need to change both the monster table migration and the monster model part of the seed script.

First, we can change all of the t.string declarations in the monster table migration to t.references. Changing only the abilities that end in "_passive" is sufficient for now because those are the abilities we created in the abilities table. The references for these abilities will actually be foreign keys from the abilities table, so we need to tell Rails that at the end of the migration:
class CreateMonsters < ActiveRecord::Migration[6.0]   def change     create_table :monsters do |t|       t.string :name       # ... The other monster attributes ...       t.references :default_passive1       t.references :default_passive2       t.references :default_passive3       t.references :default_passive4       t.string :default_skill       t.string :special_notes       t.references :lv_02_passive       t.string :lv_02_skill       # ... The rest of the lv XX abilities ...        t.timestamps     end      add_foreign_key :monsters, :abilities, column: :default_passive1_id, primary_key: :id     add_foreign_key :monsters, :abilities, column: :default_passive2_id, primary_key: :id     add_foreign_key :monsters, :abilities, column: :default_passive3_id, primary_key: :id     add_foreign_key :monsters, :abilities, column: :default_passive4_id, primary_key: :id     add_foreign_key :monsters, :abilities, column: :lv_02_passive_id, primary_key: :id     # ... All of the other _passive abilities foreign keys ...   end end
I know this seems like tedious busy work, but it's necessary for setting up the database. Some of it can be done with regex replace tools in some editors or generated with a script. I just brute-forced it and got pretty good at changing numbers really rapidly. Be sure to remove lv_xx_passive abilities that don't exist later in the list.

Because we changed this migration, we need to rerun it, but now it references the ability migration so when we rollback the migrations, we need to change the date in the file name on the monster migration to be after the ability migration before we run the migrations forward again. Otherwise, Rails will complain.
$ rails db:rollback STEP=2 $ [rename the monster migration file to a date after the ability migration file] $ rails db:migrate
We're not quite done with preparing the database, yet, because the Rails models need to know about this association, too. The app/models/monster.rb file needs to know that the passive ability attributes are associated with the Ability model using the belongs_to association:
class Monster < ApplicationRecord   belongs_to :default_passive1, class_name: 'Ability', optional: true   belongs_to :default_passive2, class_name: 'Ability', optional: true   belongs_to :default_passive3, class_name: 'Ability', optional: true   belongs_to :default_passive4, class_name: 'Ability', optional: true   belongs_to :lv_02_passive, class_name: 'Ability', optional: true   # ... The rest of the lv_XX_passive attributes ... end
It may sound weird that a monster belongs to an ability, but that's the type of association we want where the link points from the monster attribute to the ability. It's the same as if we had an Author model and a Book model, and the book belongs to the author that wrote it. The link would be in the book with the author's ID. That same link direction is what we want with monsters having links to ability IDs, so belongs_to it is.

We also need to add associations to the Ability model so that we can follow links from abilities to monsters. That association is done with has_many, which also seems a bit weird, but oh well:
class Ability < ApplicationRecord   has_many :default_passive1_monsters, :class_name => 'Monster', :foreign_key => 'default_passive1'   has_many :default_passive2_monsters, :class_name => 'Monster', :foreign_key => 'default_passive2'   has_many :default_passive3_monsters, :class_name => 'Monster', :foreign_key => 'default_passive3'   has_many :default_passive4_monsters, :class_name => 'Monster', :foreign_key => 'default_passive4'   has_many :lv_02_passive_monsters, :class_name => 'Monster', :foreign_key => 'lv_02_passive'   # ... The rest of the lv_XX_passive attributes ... end

That was the easy, if mindlessly tedious, part. The next step is trickier. We want to change the db seed script so that when the monsters are created, the passive abilities in the monster table are references to the abilities table, and we want to catch any instances where the ability doesn't exist, meaning there was a typo or an omission. We need to make sure the ability table is populated first in the script, so we have access to those abilities when we're importing the monsters. Then, for each passive ability for each monster we search the ability table for that ability's name, and assign it to the corresponding ability attribute for the monster. That assignment creates the proper reference. If the ability isn't found, we print an error and return so the error can be fixed in the FAQ text file. We'll then have to rerun the ability or monster parser and try the import again. Here's what this process looks like in code, including the ability import:
csv_file_path = 'db/monster_abilities.csv'  CSV.foreach(csv_file_path, {headers: true}) do |row|   Ability.create!(row.to_hash)   puts "#{row['name']} added!" end  csv_file_path = 'db/monsters.csv'  CSV.foreach(csv_file_path, {headers: true}) do |row|   monster = row.to_hash   monster.keys.select { |key| key.ends_with? '_passive' }.each do |key|     if monster[key]       monster[key] = Ability.find_by(name: monster[key])       if monster[key].nil?         puts "ERROR: monster #{monster['name']} #{key} not found!"         return       end       puts "Found #{key} #{monster[key].name}"     end   end   Monster.create!(monster)   puts "#{row['name']} added!" end
This code is pretty much written as just described, but note that we check if the monster[key] exists before doing anything with it. Remember that these are optional attributes, so they can be nil for any given monster ability. If we run this script, we'll find right away that Cactuaroni has an ability "Critical: Haste (RL)" that doesn't exist in the ability table. We could add this ability to the FAQ and rerun the parser, but notice that there is an ability "Critical: Haste" already in the table. This issue of the non-red-locked ability existing in the table but not the red-locked version is fairly common, so we could solve the problem with code instead of tediously rerunning the scripts to find all of the instances where the red-locked ability is missing. All we have to do is search for the base ability, copy it, change the rank to 99, and tack " (RL)" onto the name. This process can be done like so:
CSV.foreach(csv_file_path, {headers: true}) do |row|   monster = row.to_hash   monster.keys.select { |key| key.include? '_passive' }.each do |key|     if monster[key]       if monster[key].ends_with?(' (RL)') && Ability.find_by(name: monster[key]).nil?         ability_name = monster[key][0..-6]         puts "Searching for #{key} ability #{ability_name}"         ability = Ability.find_by(name: ability_name).dup         ability['name'] = monster[key]         ability['rank'] = '99'         ability.save         puts "Ability #{ability['name']} added!"       end        monster[key] = Ability.find_by(name: monster[key])       if monster[key].nil?         puts "ERROR: monster #{monster['name']} #{key} not found!"         return       end       puts "Found #{key} #{monster[key].name}"     end   end   Monster.create!(monster)   puts "#{row['name']} added!" end
If the monster ability ends with " (RL)" and it's not in the ability table, then we copy the base ability and create the corresponding red-locked ability from it. We've sidestepped a bunch of dreary work with a little bit of targeted code!

Now after running this script to seed the database, we will find a few more errors in the FAQ to fix. Four red-locked abilities are missing, so we'll have to add Perpetual Poison, Resist Damage +05%, Bonus CP, and Feral Speed. I found definitions for these abilities simply by googling them. The Resist Elements +20% ability that Twilight Odin has was also missing. I guessed at the rank of 8 for this one because I couldn't find it. Resist Elements +30% has a rank of 9 and Resist Elements +05% has a rank of 5, so it's most likely rank 7 or 8. Finally, there were three typos: the ability "Auto: Enfire (RL)" should not have the colon, the ability "Auto Haste (RL)" should be hyphenated, and the ability "ATB: Advantage (RL)" should not have the colon. After everything is fixed, we have a complete ability table with 218 abilities, and every passive monster ability is linked correctly to the ability table.


That was quite a lot of work, some of it tedious, but we accomplished a lot. We generated a list of passive abilities from the FAQ, validated that data, imported it into a new table in the database, and linked all of those abilities to the monsters that can learn them in the monster table. This process can be repeated for the much smaller tables that are left to create, and that is what we'll do next time.

Friday, August 5, 2022

Top 9 Websites For Hackers 2018

  1. HackRead: HackRead is a News Platform that centers on InfoSec, Cyber Crime, Privacy, Surveillance, and Hacking News with full-scale reviews on Social Media Platforms.
  2. Metasploit: Find security issues, verify vulnerability mitigations & manage security assessments with Metasploit. Get the worlds best penetration testing software now.
  3. KitPloit: Leading source of Security Tools, Hacking Tools, CyberSecurity and Network Security.
  4. The Hacker News: The Hacker News — most trusted and widely-acknowledged online cyber security news magazine with in-depth technical coverage for cybersecurity.
  5. Hacked Gadgets: A resource for DIY project documentation as well as general gadget and technology news.
  6. Exploit DB: An archive of exploits and vulnerable software by Offensive Security. The site collects exploits from submissions and mailing lists and concentrates them in a single database.
  7. Phrack Magazine: Digital hacking magazine.
  8. Packet Storm: Information Security Services, News, Files, Tools, Exploits, Advisories and Whitepapers.
  9. Hakin9: E-magazine offering in-depth looks at both attack and defense techniques and concentrates on difficult technical issues.

Tuesday, September 22, 2020

The Team Surges Forward


My second morning in Vermilion City was a bit more somber than that first day trying to get aboard the S.S. Anne. I took Douglas out to Diglett's Cave and said farewell to him. I had high hopes for him, but it wasn't in the cards. I was comforted that I could at least bring him back to his home and release him back to his Diglett friends and family. It was a better send off than poor Nibbles got back on Mount Moon. We had a good run, Douglas. We beat all those trainers aboard the S.S. Anne together. Farewell, friend.

I filled Douglas's spot on my team with Royal, the Magikarp I caught down at the Vermilion docks. I decided to take him with me to train in the Vermilion City Gym. I know what you're thinking! Fox, are you crazy? Why would you train the weakest of all possible water-type Pokémon in a gym dedicated to the raising and training of electric-type Pokémon? It was a calculated risk. Basically, Arnold put everything to sleep. Then Royal would come in and flop around aimlessly for a minute. Then someone else would swoop in and clean up. The only time I didn't mess around with Royal was when face-to-face with Lt. Surge himself. That was no time for training the weak, but a time to challenge the strong.
Arnold was proving to be my MVP since we met and the Vermilion Gym was no different. The same technique we used against Misty and every other decent challenge proved no less effective against Lt. Surge. His Voltorb fell sound asleep and was chopped down by Arnold's new "Cut" technique in  three simple slashes. Lt. Surge's Pikachu resisted the sleep powder at first, but eventually fell into a deep slumber and was cut down. Raichu was the final obstacle between my team and a shiny new badge. Arnold put him to sleep, but he was awake within an instant. Raichu was fast. He hit hard, but the second time Arnold lulled him to sleep would be his last time in our battle. It took a considerable amount of cutting, but Arnold soundly defeated Lt. Surge without any assistance from the rest of the team.

As I emerged victorious from the Vermilion City Gym, it was just after lunch and I was hungry. I headed into town to find some food, but stopped dead in my tracks when I saw Reginald across the street. I owed him a great deal of thanks for getting me aboard the S.S. Anne yesterday, but I wasn't sure I could listen to another minute of him talk about Rapidash. I really hoped he didn't see me, but no such luck. He came rushing across the street within moments.
"There you are, old sport!" he shouted as he slapped a hand across my back. "I've been looking everywhere for you! You were the talk of the town last night! I can't believe it. You know, I told everyone that I sponsored you aboard and helped you get into the tournament! I've been receiving people at the Fan Club all day long thanks to you! We've never had this much exposure! I really owe you a great service, old sport."
"I thought there was another trainer who did better," I asked. Chairman Reginald had his arm around me and was now leading me down the street.
"Of course! Of course! There will always be another trainer who does things better or has a stronger team than you. That's just the way of the Pokémon world, old sport, but let's not undersell what you did! You only lost one point! It was incredible. Even against that Wolf chap? Everyone had their sights on him and you came out hard to starboard and took him down - sponsored by none other than the Pokémon Fan Club!"
"Yes, I suppose that's all true," I offered mildly. I was wondering where he was taking me and if they had food there. We stopped a few blocks away from the Pokémon Center in front of a small building with a discreet little sign claiming to be the Pokémon Fan Club HQ. There was a small crowd gathered outside and each of their faces lit up to see me coming down the street with Reginald.
Official Ponyta & Rapidash Art!
"Now look, I know it isn't much, but I'd like to offer you a little something for the fantastic advertising you did for the club yesterday. All these people heard we were friends and wanted to meet you, so I have a bit of a proposition! You hang out at the Fan Club for little bit, shake some hands, sign some autographs, show off your badges - OH! You gave Lt. Surge the old lights out,  today? Congratulations. These folks will love it. Anyway, after all that, I'll hand over my bicycle in exchange. It's quite sturdy, I assure you, but - you see - I simply don't have a need for it anymore. Now that my beautiful Rapidash has stopped burning me when I try to touch him, we'll be galloping through the countryside in no time! Oh, I can't wait for you to meet my Rapidash."
So I shook hands. I smiled. I endorsed the Pokémon Fan Club. I listened to at least thirty different stories about Rapidash down to the most meticulous detail. Then when I thought I couldn't take a minute more, the crowd at the Fan Club HQ dispersed and Reginald saw everyone off. He showed me some pictures of Rapidash that he had in his wallet, then handed me the key to his bike lock. I was exhausted and starving, but I had a new way to cruise around the Kanto region. Was it worth it? Well, I felt a bit like a celebrity in Vermilion City, so yeah - it was worth it. I fell asleep that night smiling.

On my third day in Vermilion City, I ventured out to Route 11 to begin training Royal earnestly. Kiwi had done a lot of great work for me, but I sent him over to Bill's Storage and brought out Shakespear to keep him up to speed. My plan was to have Lucky put wild Pokémon to sleep and then let Royal flop around while it was safe to do so. Then Shakespear would come in and clean up. Everyone would get some decent training in. As I was riding through the tall grass, I spotted something rare and special. For the first time in a long time it wasn't a Pidgey or a Rattata. I spotted a Drowzee. He would be a formidable addition to my small team.
Royal had developed a newly awakened knack for tackling his opponents as opposed to flopping around aimlessly, and he did exactly what I needed him to do. Lucky managed to put Drowzee to sleep before he could do the same to my entire team. Royal weakened him significantly without causing him to faint and within a few minutes, Dustin was destined to be my new rising star. There was a huge sigh of relief when that Poké Ball snapped shut on the Drowsee and Dustin was now my newest friend. I would happily - eagerly! - bench Vesper for Dustin because Vesper hadn't helped me in any fight ever, honestly. Sorry, Vesper, but it's true. No amount of training could help make Vesper as formidable as a single member of my new and improved roster. Things were definitely looking up in Kanto, now.

Current Team:
Attacks in Blue are recently learned.




Bill's Storage: Kiwi (Pidgeotto) & Vesper (Zubat)

Old Man Daycare: Charlie (Pidgey)

Monday, September 21, 2020

Super Adventures With The Xbox Game Pass, Part 3

This week on Super Adventures, I'm still writing about the games I played last November when I bought a month of Xbox Game Pass Ultimate. I'm up to part 3 now because it turns out you can play a lot of games in one month if you really rush. You probably shouldn't write about them though, because you likely won't have played them enough to come to grips with the gameplay or get a proper impression of the content.

But I'm doing it anyway because I want to show some games off and I'm hoping someone reading these will discover something new they end up playing and loving, even if I didn't love it myself.

You can find part 1 here: Part 1.
And part 2 is here: Part 2.

The final part is coming tomorrow!

Read on »

Saturday, September 12, 2020

CX 2692, Moon Patrol!

This episode is all about the Irem arcade game Moon Patrol, ported by Atari via GCC. Thanks for your patience regarding the release of this episode. I had a bit of an emotional setback in January and I'm trying to fight my way out of it. M.A.D. by US Games will be the next game I'm covering, so if you have any feedback for the game please send it to me at 2600gamebygame@gmail.com by end of day 23 February. Thank you for listening!

Moon Patrol on Random Terrain
Moon Patrol on KLOV
Moon Patrol on Atari Protos
The Zorfon Mystery by Rob Wanenchak
Mark Ackerman's web site
Atari Age Moon Patrol title screen glitch
Get Matt's Repro Freeway patch on eBay

Releasing On Nintendo Switch: Unattainable Dream To Reality

By Thomas Grip, Creative director

This is one of my earliest memories. Eons ago, when I was about 5, my dad took me with him to his work, a department store. He then proceeded to dump me in the electronics department.

Nowadays you can find game test booths everywhere, but back in the day this was definitely not the case. Instead every single item was locked inside a glass cupboard. Usually these cupboards remained locked unless you bought something… but that day was different. Tony, my dad's co-worker, let me try out a game.

As I trembled with the excitement of a 5-year-old boy, he jangled his keys, and took out the showcase version of a grey box called the Nintendo Entertainment System.

Ice Climber for the NES was my first video game experience, and from that moment I was hooked.

Since that watershed moment, Nintendo games have always had a special place in my heart. Super Mario, Zelda, Mega Man, Battle Toads, Blaster Master and many others were all a part of my childhood magic. The plastic feel of the controller, the chunky cartridges, and instant-booting games still evoke fuzzy feelings in me.

Because of these magical childhood memories, and how video games were perceived back in the day, Nintendo has always had a certain mysterious feel to it – like an enchanted factory in a far-away country, creating games through some sort of wizardry.

When I started making games myself, some 20 years back, I never thought the hobby would evolve into anything bigger. It felt highly unlikely that people would want to buy anything I produced. But, eventually, what started as a hobby turned into a job. That felt so surreal. There I was, with my stupid hobby, except it was suddenly a source of income to me. Game development still felt like that enchanted factory, full of people who knew a lot more than me with tech I couldn't possibly afford to have. But it was real, as I came to realize over time.

Yet consoles, and especially Nintendo, retained a very illusory feel. While I released my games on Steam and similar stores, the birthplace of my childhood magic felt far off.
That's why it's so special to announce the following:

AMNESIA: COLLECTION IS NOW OUT ON THE NINTENDO SWITCH


Finally – Frictional Games has made it to a Nintendo console! What had, for most of my life, felt like a distant and far-fetched dream, has now become reality. Sure, it's not shipped on one of those fantastic grey cartridges, nor will it have a Nintendo "seal of quality" slapped on top, but I'll take what I can.

If the 5-year-old me heard about this, he would never believe me.

But this is by no means the end of a journey for me – quite the opposite! It's thrilling to think just how far the company has come, and it makes me super excited for what the future will hold.


A huge thank you to our friends at BlitWorks for making the port possible, and Evolve PR (with special thanks to Ryan!) for the great trailer!

Friday, September 4, 2020

8-Bit Lent


We should all have a good strategy prepared for the spiritual battle of Lent- things we are going to deny ourselves from eating, extra prayers we are going to say each day, sacrifices in terms of comfort and sacrifices in terms of media.

Lent is serious, it is the badge of being a true Catholic.

Maybe one sacrifice you might make in terms of video games is to give them up completely and to give up all reading up about them. That would be a great offering to the Lord.

Another idea, and something I am going to take up is to reduce myself to only playing 8-Bit games, (and, of course, to abstain from gaming media on YouTube).

8-Bit games certainly can be a bit of a penance, they can train us in patience, in denying immediate excitement and pleasure, and they are very very basic. They are like a bread and water fast in terms of gaming.

Give it a try. 40 days, only 8-Bit, and of course, only games you can play legally,