Tuesday, December 28, 2021

Dealing with Depression in a Post Video Game Era

Wow that's a lot of words, what does it all mean?

I want to invite you for a moment to glimpse into my gamer life. Over the course of the last 2 months I have bought the following video games for myself. Numbers are loose estimates and not the actual price:


Ori and the Blind Forest ($15)

Ori and the Will of the Wisps ($15)

Baba is You ($10)

Deponia (4 games) ($2 each)

Lost in Random ($30)

GrindStone ($10)

Loop Hero ($20)

Yoku's Island Express ($10)

Figment ($3)

Moving Out ($7)

Darkest Dungeon ($20)

Tetris Effect: Connected ($40)

Mario Party Superstars ($60)

Nidhogg 2 ($6)

Castle Crashers ($10)

Risk of Rain 2 ($7) 

Chicory ($20)

Night in the Woods ($5)

Oxenfree ($2)


Some quick calculator math later, that's 22 games and $298 dollars spent. Not bad given that some of those games were on major sale. Of those games purchased, I've put about 70 hours into Tetris Effect, and have greatly enjoyed both Mario Party and Nidhogg.

All those other games though, I've played only a little. Most I've played only once for maybe an hour or two at most, like Baba is You, Deponia, Grindstone, Lost in Random, Loop Hero, Figment, or Darkest Dungeon.

There are actually 2 games (I'm not counting Castle Crashes or Risk of Rain having bought them just yesterday) on this list I've bought and just never ever played - both Ori games. They're great games (or so I've heard) and yet I've never even started them.

Now every gamer has a large backlog, and not getting around to games is not an unheard of thing. But let's examine this slightly more in depth.

Of these games listed above I've actually purchased multiple copies for other people (presents and such):

 

Tetris Effect Connected (4 more times)

Castle Crashers (2 more times)

Nidhogg 2 (1 more time)

Risk of Rain 2 (2 more times)

Moving Out (2 more times)

Figment (1 more time)

Mario Party (1 more time)

 

Which brings my total count up to 35 games and around $575 dollars spent on myself and others for strictly Nintendo Switch video games in the last two months. That's a lot of games.


Why buy so many?


If you want my self reflective diagnoses, its a way to deal with depression I suppose. The constant feeling of wanting something new - yet always being ultimately unsatisfied with what I have, even when I acquire the thing that I once thought would bring excitement into my barren gamer life. Now, it's not all bad, 70 hours in a Tetris video game is not exactly a waste of money. A fun time in a game like Mario Party or Nidhogg is something I wouldn't trade away for any amount of money - but what about all the rest of those games?

Did I really need to buy Baba is You only to just play it once and never touch it again? Did I need to buy Loop Hero even though I know I'm not usually a fan of roguelike or roguelite games? Why buy Risk of Rain if I know I'm not a fan of that style of game at all? Why spend 40 dollars on Tetris - furthermore, why spend 40 dollars on Tetris so many times for so many different people?

Did I really need to spend 300 dollars to purchase a PlayStation 4, and then buy PS Plus on an annual subscription for 70 dollars and Kingdom Hearts 3 for 60 dollars, only to not really enjoy the game? Why spend so much money, and take so much time, playing video games that I'm either not going to enjoy, or only enjoy briefly?

What I believe is that the cost is outweighed by the fun I would have playing the game with other people. This I believe is the one thing that helps me step away from the steep precipice of overwhelming depression, is knowing that, even if only for a brief moment, purchasing and playing a video game can help me and other people have fun - genuine good fun. It is unfortunate that it is so expensive, but such is the price of medication I suppose.


Code stuff later.


Stay tuned!


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Full Circle

I began my career as an Android developer by creating a power management application for old Android phones that would turn off Wifi and Data when the screen was off. This was back in the olden days, before AppCompat and support libraries - where we needed to use Eclipse to manually download JAR files and used ActionBarSherlock to support older devices because nobody actually had any modern version of Android.

Life has changed and wow - how we have grown.

I feel it is only fitting then that I come to you today, glad to announce that things have come full circle. I am now an Android developer 10 years older.

 

 

Developing a power management application for Android devices that saves battery when the screen turns off.

 

 

Built in two days as a simple pet project to do a simple task. Now hold on, before you complain about shitty power manager applications that don't actually do anything - this is not snake oil. There is no custom power managing logic happening here, since this application is very simple. It does exactly one thing.

When you turn the phone screen off, this application enables the Android system built-in power-saver mode. When you turn the screen back on, your phone is set back to normal mode. That's it.

By relying on the built in system power-saving mode, we can ensure that a bunch of things "actually work." Battery is actually saved because the system has the ability to shut off or throttle a bunch of things. Whitelists are correctly handed because this is just the same system-wide battery optimization whitelist that you already have on your phone. Nothing actually changes about your device, this application just helps to do one additional convenient thing for you.

As always, full open source with no tracking or analytics or advertisement. I hope to have it up on the store soon.

 

 

Stay tuned!



========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Saturday, December 4, 2021

Compose, ViewModels, and Configuration

Let's talk.

First is Jetpack Compose UI. It's cool, it's new. I've migrated all supported pyamsoft Android applications to use it. It comes with some new ways of doing things. This is largely a nerd corner post.

First, the move to Jetpack Compose UI sees the deprecation of a number of PYDroid things. The entire UiView system is deprecated, along with UiViewEvents and UiControllerEvents. The UiViewModel is deprecated. The only thing that survives the migration without deprecation is the UiViewState. Let me tell you why.

First, the UiViewEvent and UiControllerEvent and UiView existed as a hacky way to create a React-like render loop, where a ViewModel would control and constantly push an entire view state. In Compose UI, this is all done for us, so all of this ceremony around binding to the normal Android view system is gone. Write Composables as you normally would, pass then props and callbacks as you normally would, and have those callbacks call ViewModel methods as you normally would. This effectively migrates PYDroid-Arch from an MVI to a better MVVM architecture.

Second is ViewModels. I am going to put forward the controversial claim: You no longer need the Android Jetpack ViewModel. PYDroid uses completely independent simple ViewModels called ViewModelers instead of Jetpack ViewModel and it's weird ViewModelStore and other initialization quirks. Why do this? Well, lets examine what exactly we need from a ViewModel that we do not otherwise already have.

ViewModel comes with 2 very important things for us - it survives configuration changes, and with SavedStateHandle can also persist data across process death. As a trade off, we receive the ViewModel class which must be injected by a ViewModelStore and a Factory, and is difficult to configure with things like scope and injection. What if we could remove all this extra baggage, and be left with a ViewModel that still survives configuration changes and process persistence? What if we could manage scoping and injection simply through Dagger as we do for every other class in our project? Well lets see if Compose UI can't help us change things around.

Let's examine configuration changes first. What if I told you that Compose UI can handle all configuration changes on it's own due to the new system? Yes, thats right. You can finally add the configChanges line to your manifest as follows:

android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

Also, be sure to override in your Fragments or Activities

override fun onConfigurationChanged(newConfig: Configuration) {
  super.onConfigurationChanged(newConfig)  
  mySyncDarkThemeFunction()
  myDialogSizingFunction()
  myComposeView.apply {
    if (isAttachedToWindow) {
      disposeComposition()
      createComposition()
    }
  }
}


This way your components will guarantee that Compose UI is kept in sync with the rest of the system like dark theme and dialog sizes and window sizes and whatnot.

This is massively important because now 1 of the two benefits of ViewModels has been effectively replaced. Now we just have to look as data persistence across process death, which can easily be achieved using our old friend onSaveInstanceState.

As you can see, we have all of the tools provided to us now to control the configChanges in our own application. No more die-recreate Activity lifecycle callbacks when a user simply rotates the phone, or opens the keyboard. Applications can be smooth and responsive, thanks to Compose UI.

Finally, let's talk about handling view state in a ViewModel. Many people like to use Kotlin data classes with val properties to represent their entire view state, and then calling copy() with individual fields changing to handle updates. This is, in fact, how PYDroid itself used to handle state internally. I'm writing today to recommend against this process, for reasons I'll detail now.

Compose runs a re-compose loop as often as it needs to keep things consistent. Let's say you've got a view like this psuedocode:

data class MyViewStateState(val name: String) : UiViewState

var myState = MyViewState(name = "")

fun onNameChanged(name: String) {
  myState = myState.copy(name = name)
}

@Composable
fun MyComposable(
  state: MyViewState,
  onNameChanged: (String) -> Unit,
) {
  val name = state.name
  TextField(
    value = name,
    onValueChange = onNameChanged,
  )
}



Each time you type a character into the TextField, it will re-compose. This will cause onNameChanged to be called which copies the myState object, allocating more memory, and then stores the variable. It's a ton of allocation and memory, which is slow, and bad.

Let's fix this, by replacing the data class State with an interface

interface MyViewState : UiViewState {
  val name: String
}

@ActivityScope
internal class MutableMyViewState @Inject internal constructor() : MyViewState {
  override var name by mutableStateOf("")
}
var myState = MutableMyViewState(name = "")

fun onNameChanged(name: String) {
  myState.name = name
}

Look at the differences in code now.

Notice how the Composable has not changed because we have correctly separated concerns of our View from our ViewModel, yay!

Now, whenever the name changes, the string will simply be assigned to an already allocated string in the MutableMyViewState, which is private to our ViewModel and to the Composable, MyViewState looks like it has not changed at all because it is now a simple interface. By doing ViewModel state this way we can avoid constantly re-allocating new objects whenever a recomposition happens.

These changes and more will be arriving in PYDroid 24.3.0, which deprecates basically anything touching the old Android View system and makes way for a Compose future.

Stay tuned!


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Wednesday, November 10, 2021

End of More Ages

Seems like only yesterday I was writing about this.

Sad to say it but it must be said - Pasterino, ZapTorch, and WordWiz have all been deprecated today and are henceforce unpublished and wholly unsupported. They will receive no further updates or bugfixes.

Long story short, Google Play Store and the Android ecosystem have evolved once again to a point where applications like these that serve as simple utility single-function applications are no longer needed nor encouraged. Rather than fill out disclosure forms regarding data I don't collect and having to comply with a continuing restrictive process, I've decided I would simply rather not invest additional work and time into hobby projects I already struggle to maintain.

They are, as of this moment, dead. Any existing versions should continue to work until they don't with any new Android versions, and if you've already installed the application you will continue to be able to use them as always. I just will not be working on them in the future any further unless the landscape once again changes.

That leaves FridgeFriend, HomeButton, and the mystery stock application alive for now - who knows what the future holds?

Stay tuned...

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Friday, November 5, 2021

A New Age

Lets talk Android.

 

Jetpack Compose UI is the new hotness, and last time we spoke I mentioned how I would begin bringing Compose support over to all pyamsoft Android applications.

First, for users.

Almost all pyamsoft applications have been converted to Jetpack Compose. This includes Home Button, Pasterino, ZapTorch, WordWiz, and SplatTrak. FridgeFriend is larger and thus is not complete yet, but support will be coming soon. Please look forward to seeing these new versions on the store within the next couple of months as bugs and testing need to still iron out any kinks. There will be no new features and the applications should keep 100% feature parity with the current version.

-----

This is a nerd corner post.

100 commits into PYDroid later and I'm happy to report that the first-version-pass support for Jetpack Compose is live in PYDroid 24.0.0!

 Jetpack Compose is, for comparison's sake, a better version of React.js but built for native Android applications using Kotlin. It's awesome.

 All PYDroid internal screens and dialogs have replaced the old XML view system with Compose views, and bridging support for themes is handled per application. Let's go over the main user facing changes with PYDroid first.

1. The PYDroid.init function takes a couple new parameters now.

googlePlayLicenseVerificationKey: String

imageLoader: () -> Unit

theme: ComposeThemeProvider?

logger: PYDroidLogger?

First, the license verification key. This is a new required parameter which should resolve to your Google Play LVL base64 string for your application. Internally, PYDroid will check the Google License verification service to make sure that your application has not been tampered with whenever you make a release build. License verification requires that you extend from PYDroid-UI ActivityBase. To disable this feature, set ActivityBase.disableProtection = true.

Next, the required imageLoader callback. PYDroid now internally uses Coil as an image loader. To allow a user to configure the image loader, this lazy resolver accepts an ImageLoader instance from the application, and uses it in the library on demand. Your application must pull in Coil as a dependency.

The theme is an optional parameter, which allows you to pass your applications Compose MaterialTheme and is provided with a ThemeProvider and a composable content callback. This will allow your Compose theme to be applied to PYDroid internal components for application consistency. Your application must implement its own Compose theme, or provide an instance of MDCTheme, otherwise this will fall back to Compose defaults.

Finally, the optional logger parameter is a new logger interface which is used in the library internally. If you do not wish for the library to show logs, you do not have to pass an implementation. As a result of this interface, PYDroid no longer depends on the Timber library, but your applications can still use Timber as a logger by implementing the DebugTree in a PYDroidLogger interface.

Now let's talk progress - specifically, let's talk deprecations.

From PYDroid-Arch, the entire UiView, BaseUiView, UiRender, ViewBinder, StateSaver, and UiViewEvent system has been deprecated. The UiView system was a way for XML Android Views to fit into PYDroid's component style architecture, which worked well to separate large screens into small independent visual components. With Jetpack Compose, this separation can be achieved inside of a Composable itself, which means that our own independent UiView system is no longer needed. The UiViewModel will continue to exist and operate with a UiController and handle a UiViewState in the MVI style, but any methods revolving around UiComponents and UiViews have been deprecated.

All PYDroid-UI which revolves around the Android View system and XML views have been deprecated. This includes XML Preference support in the SettingsFragment. Preferences should now be handled in the Compose friendly PYDroid-Ui PreferenceScreen composable, which provides a bridge between the old XML based views and the new Compose UI. Applications still using the old AppSettingsPreferenceFragment based settings screen can continue with no changes, but any new applications will be able to use SettingsFragment for Compose compatible preference UI.

All PYDroid-Util functions revolving around Views have been deprecated. As well as these, the Context.asDp() and Drawable.mutate related functions have been deprecated in favor of the Compose built-in counterparts.

The entire PYDroid-Loader module has been deprecated in favor of Coil and it's ImageLoader. Glide is large and not Kotlin-first and Coil is a smaller project and already has first party support for Compose. PYDroid-Loader was built around the old View system, and is both less feature filled than Coil and lacks Compose support.


To migrate to the new PYDroid 24.0.0, you should break up your migration into the following

1. Migrate to PYDroid 24
2. Migrate your application to Compose

To migrate to 24 is (hopefully) very simple. Provide the 2 new required parameters to your application level PYDroid.init() call (you may need to get your Base64 LVL key from Google Play Developer Console), and remove the deprecated fields from your MainActivity like the fragmentContainerId and snackbarOwner. You should be able to then build and run your application. You will notice at this point that some PYDroid UI components are not themed correctly. To resolve this you should either migrate to a Compose theme, or pull in the MDC Theme adapter project to convert your XML theme. Once you have done this, you can now begin the full migration to compose on your own time.


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, September 27, 2021

The Return

Updates are going out tonight and tomorrow for all pyamsoft Android applications!

This update brings a visual refresh to FridgeFriend, as well as minor bugfixes to ZapTorch, Pasterino, WordWiz, Home Button, and SplatTrak.

Wait

SplatTrak?

That's right! A new app, quietly released some months back! SplatTrak is a simple Splatoon 2 map rotation tracker. It's very simple and barebones right now, simply displaying the rotation for the 3 different lobby game modes, but I hope in the future to update it with Salmon Run schedules, as well as notifications that can alert you to when a map you dislike is entering the rotation, or when a map you enjoy is coming up soon!

I've also been working for the past few months on a stock related application which should help you monitor (but not trade) your portfolio as you sit at home and wait out the pandemic still after a year. I will be able to share more information soon!

As the Android landscape changes, pyamsoft continues to change with it. With the recent release of Jetpack Compose, I look forward to updating the UI of all pyamsoft apps to take advantage of this new fancy way to create delightful frontends for apps. The rewrite will be slow and take a couple of months at least, but please stay patient as I move to the latest and greatest that Android has to offer!

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Friday, April 16, 2021

PYDroid Updates

PYDroid's next version has been released!

There are major API changes to the library as detailed in my previous blog post.

Of note is the charge to the artifact path, now that PYDroid and other pyamsoft libraries are distributed via JitPack

The old artifact ID's used to be as follows

com.pyamsoft.pydroid:<library>

com.pyamsoft.cachify:cachify

com.pyamsoft.highlander:highlander

The IDs have now changed to the following

com.github.pyamsoft.pydroid:<library>

com.github.pyamsoft:cachify

com.github.pyamsoft:highlander

 

Notice the addition of github to the group ID, and the removal of cachify and highlander from the group ID 


With this change, the latest versions of the libraries are as follows

PYDroid: 23.0.1 (23.0.0 release exists but it is broken)

Cachify: 0.0.22 (0.0.21 release exists but it is broken)

Highlander: 0.0.17 (0.0.16 release exists but it is broken)


These new libraries will be used in future pyamsoft application updates, which will also be coming soon.


Stay tuned!


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Thursday, April 8, 2021

Persona 5 Strikers and Sequelitis

I just finished Persona 5 Strikers on the Nintendo Switch, a wonderful little (30 hour) game that is a worthy follow up to the original. But it suffers from a bit of same-old-sequelitis in the shadow of its predecessor. Let's briefly chat about the good, bad, and ugly.

The Good

Well, I get to see Makoto again, so that's always good. Let's be very clear. First off - I did enjoy this game. I think it was a very good one. Was it better than the original - no way are you kidding me. But does it at least deliver what I expected it to - yes and quite well in fact. So many things are still here in the spirit of the original. 

All of your friends from vanilla are here (sorry Kasumi), though this time around your time with them is slightly less meaningful. Gone are the confidant levels, so you simply shoot the shit with Ryuji because you want to, not because he's gonna give you something. Gone are working shit jobs for shit pay, now you work no jobs for no pay. Gone is the ability to explicitly romance a partner throughout the game, and gone are the player-character stat-points controlling things like intelligence and guts. There is much less life-sim between dungeons and much less RPG style growth. There is a new cooking mechanic, though that ultimately just serves as a way for you to acquire better healing items now that Strikers removes the ability to visit the doctor in your free time. There are no jail deadlines anymore - as now jails take place over the course of a single in-universe day, regardless of how many times you enter and exit. Think of Strikers as a lighter P5, that doesn't make it bad - but it does make it different. Adjust your expectations accordingly when you go into this game - its a 30 hour not a 150 hour like P5.

Let's touch on the battle system too, as this is the most significant difference from the original P5. The entire elements, strengths, weakness, technicals system is here from P5, along with the showtimes from Royal, and all-out attacks. And Gun, which works as it does it Royal and refills after a fight instead of after a run. These are, in my opinion, very faithful to the original but have now be reformed to fit a real-time action oriented battle system.

Instead of the P5 turn-based system, now you move, dodge, and attack in real time against an enemy who moves and attacks in response. This dramatically shifts how battles are approached. Before, some battles would literally not be possible by the sheer fact that, if you were level 1 fighting a level 10, the enemy would absolutely win because there was nothing you could do to avoid an attack directed at you. In this new system, you now have player agency, and could in theory take on and defeat a boss 100 levels higher than you, just by dodging and whittling down it's health. Interestingly enough, this actually makes random battles less fun, and you are slightly less motivated to fight because you know that any boss no matter how big can be out-dodged. It does however, make battles more exciting, as you now are the single most important variable in deciding whether your party wins or loses the fight. With this new system comes your new greatest enemy though. The Reaper don't got shit on the Camera. The camera spinning around violently or clipping into a wall during a fight will get you hit more than anything any shadow can put out. If you can survive the camera though, the new system is a lot more engaging and involved. Gone are the days of press start to watch the game play itself. Gone are the days of Morgana moves faster than I do and he always goes first and the shadows are weak to wind. Gone are the days of Rivers in the Desert playing to start off the epic "last" boss, before shifting to buff guy Shido just standing there while you pick whether to smack him with a knife or eat a parfait. Now when Rivers starts playing, you know the fight is about to go down. And you'd better - because Rivers is used and re-used in the end game for maybe 3 or 4 or 5 different fights, so I hope you like it.

The Bad

Re-use. So many things are re-used. While character re-use is generally a positive, almost all music is the same, or remixed from P5, which is both cool, and not cool. Even though Last Surprise is super dank - it's also the song for Persona 5, just with some instrumentation changes. Same with Rivers in the Desert, same with Life Will Change or Beneath the Mask. It's cool to hear these songs again, but we've lost out on being able to fall in love with another banging Persona soundtrack because they've instead opted to re-use hit music. Such wasted potential it seems like.

Another big re-use though, is story structure. Strikers follows an almost identical story progression, so much so that the character in game remark about how similar places and events are. While this generally helps tie the story of this game back to the first game, it also brings the focus front and center on just how similar everything is again.

Game starts. Ryuji, Morgana, and HaremFucker69 are thrown into the Metaverse unexpectedly, and then thrown into a prison. First dungeon is Ann's story, second is Yusuke's. A person of "the law" is trying to take you down on the side. You meet someone early on who is in a respectable position in society and you generally like them, and they help you. You go to the beach and have a party. Ryuji makes a boob joke. You and Makoto fall in love. Police person converts to your side. You beat the final boss, politics guy who thinks he is a hero, and then the game says "wha-oh guess what that wasn't the final boss" and then it drops you back into a Mementos dungeon for the real final boss. Nice person betrays you. Guess what, still not the real final boss. Metaverse and real world combine except only your party and the villain are aware. You climb a skyscraper and then get to the real real final final boss boss, and it's second form the superduper final forreal endboss boss. You win. Everyone else forgets, and things go back to normal. Boss makes coffee and curry. Credits roll, but the music is not Hoshi to Bokura to, so you throw your controller at the wall.

Let me be very clear, Strikers is not the masterclass of a game that vanilla was, but its also not trying to be. Strikers' story is passable, and let's be honest you don't really care. If you are in to Strikers, then you are here for more game with the P5 characters you've come to enjoy, but with a new action-orientated battle system. You know just as well as I do that if Strikers was just the Persona 5 characters sitting and talking in Leblanc doing generally nothing for 10 real time days, you'd play it too. You're in too deep with the cult and you can't leave now.

Strikers is a solid follow up to the game you've come to love, but I do not think that on it's own it stands as a strong title. I would certainly not have picked this game up if I had not played 5 first, and the game seems to agree since it explains almost nothing at the start because, well, you already know right. You played Persona 5 right.

But, at just below 2 dollars per hour of your time, I would say that Strikers is highly worth it. It's a very solid game - though admittedly just more of the same working formula - but I could see myself replaying this in the future, though, only as a follow up after replaying the original one first.

The Ugly

Morgana is extra simpy in this one.


Code stuff soon, stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, March 22, 2021

Spiritfarer and game exhaustion

Spiritfarer is a wonderful game. It's charming - the artstyle feels hand drawn and light and lively. The characters are emotive and huggable. Your travelling companion is a cat. You grow gardens and weave and cook and herd sheep and plant trees and catch fish. You play music and chop logs and smash rocks and glide around through the air and play with movement physics. I love it, it's a magical first-time experience game.

And I've never finished it.

I purchased Spiritfarer back in August of 2020, and played it for 1 or 2 hours each evening after work for about a month. I took it slow, as I was never the person for these kind of games, and had just gotten done seeing my girlfriend take a break from Animal Crossing for literally the entire month of July after playing it every day for 3 months, so I knew that the grind of playing a game every day could be killer. I stuck to only a couple of hours each evening. In that time with a couple hours each night, I was able to meet and send off Gwen, Summer, and Alice. I took a break from Spiritfarer around October - one evening I simply didn't turn it on like I had every other night for the past month or so, and my planned break of that single evening turned into days, into weeks - into months. I started up once more in January of this year, helping Atul find his Fried Chicken and started planning his dinner party (though he's still waiting for Gustav's Tuna Tataki before we can sit down to eat). I played again for 2 or 3 days before once again putting the game down, and I sadly haven't picked it up since.

Let me state that I do not think Spiritfarer is a bad game, it's just simply not my game. I played Spiritfarer for what I estimate is around 40 hours, and enjoyed it. It was not at all the kind of game I usually play, and frankly I'm surprised I stuck around for so long. I think the reasons I left the game mainly boil down to two things - uncertainty and exhaustion.

Let's touch on what I mean by uncertainty first. The beginning hours of the game are masterfully crafted. You meet Gwen and she takes you through the game's basic tutorial where you discover how things work and what kinds of things you will be expected to remember during your sessions. You learn about movement, arranging the boat, cooking food, spirit food likes and dislikes, spirit skills, and the basic "crafting" system. You meet Summer and learn about gardening and music and where to go when you need to find resources that are usually scarce. You learn more about Gwen as her backstory opens up, and you discover the locations relevant to her past and her journey. You send her off through the Everdoor. Maybe you cry a little.

And then - what now. The tutorial is done, the Spirit whom I've grown the closest too on my boat is now gone, and I'm left with Summer, Atul, Alice, and Astrid. Atul is easier since he will eat anything, but gone are the days where it was acceptable to just rotate between popcorn and coffee - now I need to actually start making food. Oh but, I've only got access to Coffee Beans, Corn, and basic thread, so if I want to start making foods that Summer and Alice will actually enjoy, I need to go find more materials. My first soft wall in the game that I've picked up as a way to de-stress - everyone wants something but nobody will tell me where to find it. I open up the map and no dice - I've visited all of the locations on my map already.

But wait, what about the dark zones on the map? What if I just go off into one of those zones and hope that I'll find the magical city of cabbage and fried chicken? Beats going around looking for crates in the hopes that one of them will have not-coffee-beans.

Looking back, I now realize that this exploration is exactly what I was meant to and expected to do. The game wanted me to venture off into the far unknowns and organically discover new places and new things. I did, and I enjoyed it! But, up until that point for me the destination had been mostly laid out and setup for me, with the only obstacles on the way being making sure I had enough materials, or had shown mastery of the loom and the sawmill. I had never been required to find a new location, but rather had always been gently or forcefully guided towards that outcome. Suddenly with Gwen gone, the game had decided that I had made it to the big leagues after just one Everdoor trip, and now I had to play or go home.

Armed with my new mindset to go wherever until I was stopped by ice or rocks, I eventually succeeded in finding Bruce and Mickey, Giovanni, and Gustav. I also helped see Summer and Alice off through the Everdoor. It was at this point that the game took a major turn for me and I had to put it down for the first time. It was at this point that the game became more of a chore, and the chore became exhausting.

Let me briefly speak on one point where I believe the game - as a game - does fall a little flat. The opening hours are supreme - there is no other feeling quite like picking up Spiritfarer for the first time. But as the game continues on and characters come and go, one thing I began to notice was that as I moved further and further into the game, the characters became less and less engaging. I sympathized with Gwen and was patient enough to wait for Summer and Atul, but my initial reaction to meeting Astrid and Gustav and Bruce were not those of patience. I didn't resonate with these later characters as much, and as a result never actually cracked their hard shells enough to get to the meat of what their stories were. Instead of the game building up more and more of it's wealth of characters and ending with a bang, it was beginning to fizzle out before it had finished. When I left the game, I was in the middle of Astrid and Giovanni's infidelity, and had just gotten done building homes for Gustav and Bruce, and I just couldn't be bothered.

I liked Gwen, really liked her. She had personality, she helped me through the first parts of the game - held my hand so to speak. Normally I'm not for that kind of thing in a game, but in Spiritfarer? In a game which I had hoped would be relaxing and stress free? In a game with wonderful music that allows you to hug all the characters? Heck yes I would want someone to help me along with a light suggestion of, at least, what to do next.

I liked Summer too, though to a lesser degree than Gwen. She and I never quite clicked the same way things were with Gwen. Alice was even less so - though I empathized with her story and how tough it became for her in the end, we never quite hit it off. It seems that I was opening up less and less to the later characters. In a strange way, Spiritfarer had burned me in a way I never thought possible.

I was afraid to become engrossed in these new relationships with my other characters because, after Gwen left, there was a hole that was left unfilled. In both a figurative emotional sense, as one of the characters I had come to enjoy was gone forever, but also in the literal in-game sense - as her house was left on the boat but abandoned, unable to be removed or re-occupied. As the exact opposite of the principal goal of the game, I had not learned to cope with loss, and I had not learned to let go - rather - I had become more fearful of the idea than ever before. If I let go of Gwen, who was my favorite, and was miserable, what would happen when Summer and Alice and Atul left. I'd be stuck with Astrid, and Giovanni, ugh, or even Gustav. Or Stanley.

So progress would damn me, as I would be left with only characters I genuinely would not want to be on a boat with. But a lack of progress would also damn me just the same. Astrid would constantly complain for me to see what Giovanni was up to. Atul would keep asking for Fried Chicken and Pork Chops. My days in the game went from exploration and wonder into the very planned and carefully calculated game of optimally watering plants, picking crops, crushing seeds and rocks, smelting ores, travelling to the next city or event location, and cooking in preparation of whose favorite dish I would be making that evening. Spiritfarer became a stressful experience, just because there was always so much micro-managing to do. And to do it for characters that I wasn't really meshing with didn't help.

Let me put it out there, that I know Stanley had a troubled past for a mushroom-thing, but boy do I despise him as a character. He shows up, basically out of nowhere, and immediately starts demanding resources and consuming space. I was not prepared at all for Stanley. With the other characters, I could at least decide to speak with them once I knew I had space on the boat, or enough food to last a couple nights, but Stanley literally pops out of the ground and then suddenly demands food and takes over my guest house. Not cool Stanley.

This large amount of micro-managing took a once relaxing and genuinely enjoyable game into a chore combined with the fun of optimizing my route around the ship and the arrangement of my buildings for the shortest path with the least button presses. Yeah. That was how I survived what had otherwise become the monotony of watering crops and smashing seeds and cutting logs. Optimizing.

I saw off Gwen in August, Summer in September and Alice in October. I was so burned out that I put the game down in October and didn't pick it up again until January. In January I saw off Atul, and then returned to a ship with Giovanni, Gustav, Astrid, Bruce, and Stanley. I looked at my fields of crops that needed watering, and my sheep that needed trimming and my food that needed cooking. I looked at my characters demanding simple food, but also exotic food, but also nothing with veggies or fruit. I looked at the quest I had to go to a town which was locked behind an ice-walled zone because I didn't have a big enough ship. I needed metals I had never heard of from places I had never been.

I put the game down and haven't picked it up since. I probably will never pick it up again.

I did not dislike Spiritfarer, on the contrary, I loved it and would actually recommend it to people who are more easily swept up in these kinds of micromanage/simulator/farming style games. It's not for me, but it is for somebody. I'm just exhausted and burned out from playing it too much - and if I have to wait for the slow animation of that tiny bird turning that huge bull around before the dialog box pops up one more time - I'm gonna lose it.



Code stuff soon, stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Tuesday, March 16, 2021

Libraries and You

It should come as no surprise to you at this point that JCenter will be shutting down. To prepare for the future where PYDroid libraries will no longer be hosted on JCenter, I am preparing to release the next and all future versions of pyamsoft Android libraries via JitPack

In order to prepare for this change, you will need to update the artifact ID for the next release. Instead of releasing under the usual com.pyamsoft.pydroid or similar artifact groups, it will be released under com.github.pyamsoft.pydroid. The same transition goes for Cachify and Highlander as well, which will be getting a new com.github prefixed artifact group. This will not change the package name.

With this change comes some important news about the next version of PYDroid, which will be a new major version - 23.

A major change is coming for how UiViewModel classes work with regards to creating a component. Before, you would have created the following kind of ViewModel





Notice how the ViewModel is strictly tied to one kind of ViewEvent, and knows how to handle all of those ViewEvent messages itself? This was largely nonsensical - as a ViewModel would not be connected to a ViewEvent publisher without wiring it up to the Component mindset via createComponent, which means that there would be a UiController present. This also led to issues that made it difficult to re-use a ViewModel with only a partial set of UiViews, as there would be ViewEvents which, in the context of the Component used, would never be called, but the ViewModel required handling it anyway, which was a code smell.

The new way in version 23 and onward will significantly change the ViewModel and createComponent calls in a few very important ways. First is that the UiViewModel no longer requires a strict ViewEvent type to be provided as part of the class signature, and as a result, will no longer support the handleViewEvent method.



The second significant change is that when creating a component via the createComponent function, a controller parameter is now explicitly required. The controller can be anything that inherits the UiController<C : UiControllerEvent> interface. You can either have your Android Controller class (Activity or Fragment for example) inherit this interface, or use the convenient inline method newUiController which creates an anonymous controller. The inline method is useful when a single Android Controller class handles different UiControllerEvent messages.

By changing component creation in this way, the UiController now correctly drives all interactions between its individual layers. A UiView publishes a ViewEvent to the Controller, which handles it in the trailing lambda of createComponent. In many cases, this ViewEvent will be call to a UiViewModel method, which can set UiState, or publish a ControllerEvent. The ControllerEvent is then handled by the controller passed into the createComponent method. This allows you to bind the same ViewModel to multiple different ViewEvent types, since the ViewEvent class type used in the trailing lambda will be decided by the kinds of UiViews that you pass to the function.




PYDroid 23 still has a ways to go before it is ready for release, but keep in mind that these changes will be coming soon. Because it is a major code base overhaul, this release will not be backwards compatible, so you may have to invest some time to migrate apps over to the new version. I was able to migrate all of my applications in a single day, though your time spent may change based on the size of your application. If you want to get a head start, you can pull a SNAPSHOT version off of JitPack.


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Wednesday, February 24, 2021

pydroid

A small update has been released for PYDroid bringing it to 22.6.1.

The only API change in this release is hopefully minor - it changes setState functions to allow for the stateChange lambda to be a suspending function. For Kotlin users there should be basically zero code change. For Java users, this will change the function signature because in Java suspending functions are implemented as Continuation resume callbacks. While this change may be slightly more painful for Java, as Android continues moving to a Kotlin first ecosystem modern codebases must be ready to follow. PYDroid is Kotlin first and Java friendly, but I cannot make Java compatibility guarantees as I largely do not use Java anymore.

If the change to suspending state change callbacks causes issues, please file a bug report and let me know.

Stay tuned! 

Saturday, February 20, 2021

Quick updates

A quick patch release was pushed for FridgeFriend that brings it to version 32. This fixes a bad buggy in the Group detail screen where the list of Items would normally be present. A bug was causing this list to be blank until at least one letter was inputted into the Search box - the recent update will fix this issue. Please update as soon as possible to get the latest fix!

An update to the Discord stock bot was released that brings very basic support for the nearest weekly options that are close to or in the money. Unfortunately, the options info is still incomplete, as I would like it to at least be able to list monthly or leap options, as well as provide delta info. I will continue working on this to make it more complete, but basic support is there for now.

An update to bubblewrap-wrap has been pushed which brings better isolation for user-specific runtime directories.

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, February 15, 2021

new stuff

Some quick updates

My nameless discord stock bot has gained basic support for displaying the upcoming weekly options chain for a given symbol.

ZapTorch has been updated with support for toggling on and off the various double click volume commands, so you can use some or all of the different volume commands.

FridgeFriend has been updated with simple bugfixes and a change to the search screen (although this one is sitting in Google review limbo)

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, January 25, 2021

New FridgeFriend

A new version of FridgeFriend has been released which includes some major changes!

First is the removal of the Map screen. This screen was originally envisioned to let you mark store locations on your device so that FridgeFriend could notify you when you were close by via geofencing. It was intended to be a hands off operation, so you wouldn't need the app open and could be, for example, driving around in the car and the app would notify you as you passed by a store. Unfortunately, the Google API and Policy changes with the new Android version made this feature much less useful - as you had to have the app open for it to remind you about nearby stores - and with the change in how society functions due to the pandemic, most people are not out and about anymore (and if they are, they usually have decided in advance to go to the store already). As such, the feature rarely saw any use and was not very useful when it did, so it has been dropped. A side effect of this is that FridgeFriend now requires zero permissions.

Instead, a global search screen has been added. Now when searching on this page, Items from any Group will be returned to you, allowing for a higher level birds-eye view over all of the things in your fridge. This way, even if you have items spread out among multiple Groups, you will be able to manage them all here as if they were in one large "super group".

The update has been published and should be rolling out to your device soon!

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

New pydroid

PYDroid version 22.6.0 has been released.

This officially removes the old deprecated methods, like saved instance support on plain UiViewModels.
This is largely the same release as 22.5.4 just with deprecated methods formally removed, but there is one change that can impact your application's behavior.

The setState function used to take an optional parameter andThen which would run only when the state was actually changed. This behavior has been modified, and the andThen callback will now run after every setState operation, regardless if state actually changes or not.

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, January 18, 2021

More state less bugs

PYDroid 22.5.4 follows quickly on the heels of the last release and brings some changes and some fixes that are pretty important, if I do say so myself.

The first is the removal of the SwipeRefreshLayout.refreshing(Boolean) extension function. This function was originally created to work around a bug in the SwipeRefreshLayout library by posting to the setRefreshing function, but later versions of that library fixed the bug so the extension function is no longer needed.

Another important change is to the setState mechanism on ViewModel and StateModel classes. The setState function used to open its own CoroutineScope which could not be cancelled except by cancelling the entire scope. This API still exists but may become deprecated in the near future in favor of the new APIs, which are extensions with the same signature but on the CoroutineScope object. As this is only a bugfix release, the old functions will stick around for the future, but may be removed in the next minor release.

Please upgrade to 22.5.4 when possible.

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Thursday, January 14, 2021

pydroid and state saving

PYDroid has received an update to version 22.5.0! Major changes include new support for the androidx.savedstate module in UiViewModels! This support is optional.

UiViewModel will be deprecating the doOnRestoreState and doOnSaveState hooks. In the future, the UiViewModel class will no longer support instance state saving. For ViewModels wishing to use and write instance state, the new UiSavedStateViewModel class should be used (long names - I know - what is this - Java?). The UiSavedStateViewModel class takes in it's constructor an additional UiSavedState parameter, which is a simple abstraction over the SavedStateHandle class from androidx. You can access the UiSavedState via the restoreSavedState, putSavedState and removeSavedState functions.

Currently the UiSavedStateViewModel class is seperated from the UiViewModel class, but once the saved state migration is complete in a future version, the saved state view model will extend from the normal view model as a base class. Consumer API will not change, but ABI compatibility will not be maintained once this happens.

To facilitate these new ViewModels, a new factory has been created, called SavedStateViewModelFactory. To create simple factories, the new inline functions createViewModelFactory and createSavedStateViewModelFactory can be used to create simple single-value factories so that your custom ViewModels can play nice with Android's ViewModelStore system.

Along with these changes, the UiBundleReader and UiBundleWriter interfaces have been deprecated. For library consumers, this change is largely unimportant - but one thing that will affect consumers is that the UiBundleReader methods have been reduced to just the get(String): T? method. All other methods in the future will be removed. UiView consumer code will be largely unchanged, as long as all reader access is changed to only use get(String): T?.

The UiRender interface has removed support for the distinct function as it did not make any sense in the given context, and has renamed the distinctBy function to mapChanged to better convey how that function is used. The asUiRender extension function has been changed from being an extension on Any to being an extension on UiViewState only, so that random objects cannot be turned into renderers unintentionally.

PYDroid ui has been updated to use these new systems and now lazily initializes all of it's components, which should reduce the start up time cost. An optimization has been made to coroutine scope creation which should avoid a large time penalty during debug mode builds. In the future, support will begin for using UiViews as composable building blocks, but this will not be at least until compose-ui enters an API beta with stability guarantees.

Finally, some functions are exposed to a Java consumer that were intended to be internal to the library only. These functions have since been moved into an Internals class. You should not use these functions as a PYDroid library consumer - if you do, there is no support and functions can be radically changed or removed without warning. Don't use anything labelled internal, if you are able to access it, it is a mistake.

The bintray official release will be going up later today, stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Wednesday, January 13, 2021

Monster Hunter and being Prepared

Monster Hunter is a series about being prepared. While not a series for everyone, for those who do enjoy it, it can be one of the single most addicting things they play. The game rewards a player being prepared for any number of situations. Let's explore briefly how being prepared is rewarded in the latest entry of the series, Monster Hunter: Rise.

Note: These are impressions gathered from playing the Switch demo of the game in a multiplayer context. I am a beginner, as you will soon figure out.

Monster Hunter is for the patient gamer. The game almost downright requires that you study and practice before going off on a hunt. Before you hunt, you have to pick a weapon class. Before you pick a weapon class, you need to know how each performs to see what you like and don't like.

Before you can hunt, you need to go into weapon training. You need to pick a weapon and start trying it out - and not just pressing random buttons either. Monster Hunter is a slow game. If you start an attack, you need to already know in your head how you plan to end your attack, and you need to know when you are committed to finishing an attack animation for large damage, and when you need to stop the attack and dodge. To be prepared to train, you need to first read the weapon combos and understand what the "goal" of a weapon combo is.

Take for example, the Charge Blade. The "goal" of this weapon in it's normal usage is to unleash a fully charged Amped Element Discharge attack. In order to do that, you'll need to build charge. In order to do that, you'll need to land the standard sword combo first, which is X, X, X, then follow it up with ZR so that you do not enter the slower Axe mode before you are prepared with charged Vials. But you need to also not push ZR too early, or you will bring up your shield and enter Guard, which is not what you want to do. Once you have charge, you need to store your charge by switching to Sword mode and pressing ZR + A, which will store one set of Vials. Rinse and repeat this until you have two charged sets of Vials, and then you can enter Axe mode and release a fully charged Amped Element Discharge attack.

But when should you release your big attack that you've taken so much time to build up? Not when the monster is moving around obviously. You need to first knock the monster down and make it so that it is stunned for long enough for you to get the fully charged attack off. In order to do that, you need to understand it's weak points and strike them repeatedly.

Now that you are prepared, and have on where to hit the monster and when to use your big attacks, you need to hunt the monster. You can't kill what you can't find.

So you have to prepare to fight by finding clues around the map that can help lead you to the monster. Droppings, skeletons and remains, tracks - all of these are clues that can help build an understanding of what monster you are looking at, and how to find it.

In order to prepare to track the monster, you need to also prepare properly for the excursion. Eating the right food, gathering the right equipment, heck, even being the right class can help you better prepare for the hour long quest ahead.

All of these things stem back to the core gameplay focus - being prepared. The game rewards you for being patient and planning out all you can about your encounters. By being so rehearsed, you will be able to keep up when the game suddenly changes your quest in the middle by throwing in a second monster or a bigger monster, or replacing your quest target with a different monster entirely.

And of course, you need to be prepared to play with other people. Monster Hunter is a lot more fun with friends.

Code someday, stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, January 11, 2021

Exploring the strategy in Monopoly

This is inspired by a recent conversation I had with my girlfriend, which ended with her stating:

"Why do people like Monopoly so much. All you do is buy a house, what the fuck?"

In this post I wish to explore the sentiment expressed by this reductionist viewpoint to see if, in my own understanding, Monopoly is truly no deeper than rolling dice and buying property. As a slight spoiler before I begin - I believe the game is ultimately deeper than my girlfriend gives it credit for.

Using a standard board, there are a number of different properties on the board that are available for a player to purchase. The board is broken up into 4 sides, which I will call "legs" for reference. Each leg is broken up into a couple of different sections which bear the same colors, which I will call "color groups". Let's break down the spaces that are most valuable, and lay out some general tips for the best games.


The strategy in Monopoly revolves around, ultimately, one very simple question:

"How long will I have to wait to make money if I buy Boardwalk?"

Everything you do should be based around the odds of making the money you've spent back in the lowest number of dice-rolls possible. This also means that there is a method to how much you should build on properties, as well as which properties you should pay money for and which you should avoid.

Control Color Groups
You should always purchase property with the mindset of figuring out how many turns it will take before you have been refunded for your spend. To this end, you must seek to control entire color groups for the most payout. The only time you should find yourself holding a single property outside of a group is when you are strategically holding it to prevent another player from completing a group.

The railroads
The railroads are very valuable for a player to own, but the maximum benefit comes from owning all 4 of the railroads. This is because the rent for a railroad starts at $25 and doubles with each other owned railroad. Because there are various event opportunities in the game which move a player to a railroad directly, and there is one railroad on each leg of the board, the railroads are the most important spaces in the game. You should always look to own all 4 to get the highest payout, owning 1 or 2 is only good when strategically preventing another player from holding the set.

Optimal house count
The optimal number of houses to have is 3 houses on most color groups. The one generally accepted exception to this rule is in the case of the dark-purple color group which is the spaces immediately in front of GO, where it is more optimal to have a hotel. The reason 3 is the optimal number in most cases is due to the cost of a house as compared to the multiplying factor it brings. The first house is always a must, it usually jumps the rent 5x from the empty lot value - Virgina Avenue for example jumps from 12 to 60 dollars on a single house. From there, each additional house brings around 3x in value, so up to 3 houses is generally optimal. Past this point, 4 houses or a hotel cost significantly more without as large of a multiplying factor (to prevent the game from getting overly large rent values). A 4th house usually brings in only an additional $100 dollars, and a hotel usually only an additional $200, despite costing the same as the first three houses. Instead of spending money on the 4th house and the hotel, you should look towards expanding the number of properties you own instead.

Property Priority
The best properties in the game to own are on the third and fourth legs of the board - the orange section with St. James and friends, the red section with Kentucky and friends, and the yellow section with Marvin Gardens and friends. This is because, due to how the dice rolls work, a person is statistically most likely to land on the orange section of the board. Following the orange roll, they would next visit either red or yellow. Quickly capturing and building property here can help you get a commanding lead in the game.

Stopping Group Ownership
It is highly important that you opportunistically purchase properties outside of your currently owned color groups, so that you can avoid other players having total control over a color group.

Go to Jail
In the early game, getting into jail can mean you lose opportunity to purchase other property, so you should always immediately pay the fee to get out so you can roll to claim new property in the early game. Jail is your favorite place in the late game though, because jail is free to stay. You should always take the opportunity to go to jail, because it avoids you needing to move around the board and putting yourself at risk of paying a large amount of rent to another player. While you are in jail you still collect rent, so its a win win! Late game Monopoly strategy can be summarized as: "The best way to win Monopoly is to not play."

Be nice
People are creatures of emotion. While not always a guarantee, if you are nice to another player, they may be nice to you. You should always try to be nice to the other players so that when that critical property goes to auction you can convince them to not challenge you - or you can sweetly whisper away that critical single railroad that you need while you offer them a large sum of money for it. Being nice can often make other players put their guard down and deviate from the optimal strategy, which can in turn allow you to correct mistakes you may have made. Be nice!


---

These are just a small set of essentially robotic strategy that produces the most optimal result. The variety of the game comes from the fact that no situation will play out exactly as this ideal scenario has. People will refuse to give up properties, or give away properties to your opponents. You will find yourself rolling poorly and never claiming full color groups, or you will find that your statistically high property is never landed on. The game is ever changing because of the people involved, and you must always be willing to adapt to your situation at hand. Also, remember not to take things too seriously, don't get frustrated if other players don't seem to be following an optimal strategy. It is, after all, just a board game.

As we can clearly see, Monopoly is at is core much simpler than just buy a house - roll a dice - win a game. In a reductionist viewpoint yes, those are the core steps, but the actual behind the scenes brainwork that goes into deciding where to buy a house, and the slight social manipulation evolve the game into something far deeper. Monopoly may not ultimately be the deepest thought worthy board game out there, but it does at least possess more depth than simply rolling dice and buying houses.


And finally, let me end with this - this is not a conversion attempt. If you dislike Monopoly I am not attempting to change your opinion. Regardless of how I understand the game to be, any perceived depth does not ultimately change whether or not the game is fun to play. If you like it, you like it. If you don't you don't. No amount of strategy discussion will ever seek to change that. I hope that you can assess the game as something which objectively can have depth and strategy, even if subjectively it may not be entertaining.



Code stuff next time. Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Friday, January 8, 2021

libs and more

Cachify has been updated to version 0.0.20 to fix a pretty nasty bug related to memory cache storage. This change comes with some major API changes.

The CacheStorage<K, V> interface is now a CacheStorage<T>. A CacheStorage interface now handles just one piece of cached data instead of requiring support for multiple pieces of data.

The entry points at cachify and multiCachify now take a lazy list of CacheStorage implementations instead of requiring you to initialize the CacheStorage at the callsite. This is so that multiCachify implementations do not all accidentally attempt to write to the same cache storage. CacheStorage instances are now separated between Cachify instances. The MultiCachify key function is now suspending so that we can guarantee the mapped backend has only one writer at a time. There is also a strict guarantee that a list of CacheStorage instances will be written to before being read, to make sure that data in the cache is consistently represented. All of these backing storage guarantees are handled by a simple suspending Mutex.

PYDroid has been bumped to version 22.4.4 which brings built in support for the In-App Billing v3 library. The library has a new billing module which handles the heavy lifting, and a reference implementation is built into the PYDroid ui library, and automatically initialized for you when using the ActivityBase class. A default interactive UI is also implemented in the AppSettingsPreferenceFragment class, via a "Donate" list item. The list will be populated out of the box if your IAP items use an id scheme that looks like: <package-name>.iap_{one,three,five,ten}. Currently there is only support for donation style consumable IAPs.

An update has been pushed for FridgeFriend which brings support for moving Items between Groups. You can better organize the stuff in your fridge by breaking large groups of items up into smaller subgroups, and this moving option can better facilitate that.

ZapTorch has gained support for additional button gestures. Double tapping the Volume Up button will cause the flash light to slowly pulse, and pressing one Volume Down followed by a Volume Up will cause a quick strobe effect.

I will formally publish the updates to the store once testing has been completed, probably today or tomorrow.

Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Tuesday, January 5, 2021

pyamsoft apps

In the coming days you may notice that pyamsoft Android applications on the Play Store are being published under a new developer: pyamsoft apps

Rest assured, this is still me.

I had to change the developer account associated with the applications so that I could link a new Google Merchant account. This is because my previous Google merchant account was closed back in 2018 by mistake and I did not realize the impact that would have until much later.

The reason I need to link a new Google Merchant account is so that I can begin experimenting with things like Paid Applications, for example, or in-app purchases. Rest assured, all pyamsoft applications will continue to be open source.

You may notice this change taking effect in the next couple of days.

Stay tuned!


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Monday, January 4, 2021

My Favorite Games of the Decade

Video games are different things to different people. To some, they are an escape. A chance to explore a world that doesn't exist or live a life so different from your own. A chance to meet new characters and see them grow - to experience change and growth with them at your own pace. A chance to invest yourself into learning something new and perfecting it - a chance to be better than your friends. A chance to explore a medium of something beyond just buttons and lights on a screen. As 2020 puts the bookend on a long year, I would like to do something a little out of the ordinary and look back on some of the best games I had the privilege of experiencing this past decade. I played plenty of games, but these are the ones that left the most lasting impression on me, released between 2011 and 2019.

I will preface this by saying that I have finished all of these games - not 100%, but enough to the point that any main story or main activity is done, and to the point that I consider the game "complete" for me. This will be an unordered list of games of varying genre and target audience, and does not reflect what I believe to be the best games - just simply my favorite ones. I'll try to include release dates and links where appropriate.

 

Warning, I'm not a writer. This is a windy mess of opinions. Language. Spoilers.


The Legend of Zelda: Breath of the Wild

2017 (Played in 2017)


What better way to begin than with a game that single-handedly revolutionized the open-world genre broke the mold on so many expected conventions, both in the open-world genre and it's own game series. I could gush for a while about this game and how it changed the way I see value in video games, but I won't - as plenty of other sources on the Internet have already done so, years before. I could also complain for hours about the many various shortcomings of the game - but again - others have already done this better and faster than I could.

The reason I include this game on my list, is for the feeling of escapism provided by the in-game world. When first presented to you, Breath of the Wild's overworld is massive and daunting. With no concrete requirements, you are thrown paraglider first into a world that no longer cares what you decide to do in it. Beat the final boss in 5 hours or 100 days, it doesn't matter. Complete one dungeon or all dungeons or no dungeons, it makes little difference. Find every tower or find none, so what. This extreme sense of agency, though terrifyingly paralyzing at first, became my favorite thing about the game in my hundreds of hours played. 

Before Breath of the Wild, I used to only play games which featured short bursts of constant action. I didn't care too much for story or world building, since I was much more concerned with having the time to pick up a game and play for 20 or 30 minutes before returning back to those more important things in work and life. The games I played were all about shooting, or fighting, or doing something, and usually featured short matches or sessions that were self contained.

Breath of the Wild threw all of those preconceived notions about what I looked for in a game out the window. I learned to slow down. I learned to appreciate the empty moments in between roaming from landmark to landmark. I began to love the softer, spaced out sounds of the musical selection. The quiet, companion-less, lonely existence in a wide and wild world. The childlike whimsy of discovering something new, or noticing something for the first time. The autonomy of doing whatever I wanted without worrying about expectation or disappointment. Something clicked for me when I played this game, and it has forever changed the way I view the medium.


Persona 5

2016 (Played 2019)


This is certainly not a game that I expected would resonate with me so much.

To begin with, I hate turn based games. I hate JRPGs. Hate them. Click a button, watch some text. A guy does some animation that I've seen a hundred thousand times, only to miss - gah I wish I could just skip all of this useless timewasting bullcrap. Spend hours grinding in a zone I've already completed so that I have the slightest chance of defeating a boss who's name I can't pronounce to save a planet I don't care about to further a story that lost me the second cutscene in. Waste of my time.

Oh how wrong I was.

This game, this beautiful game.

I loved Persona 5. A Japanese, turn based, RPG game that gobbled up 150 hours of my time in 5 weeks and then when it was done spit me out the other end a broken man with a large anime shaped hole in my heart. I amount of enjoyment I got from that feeling of escapism as I lived out my school year as a high school boy in Tokyo cannot be understated. I loved walking around town and talking to people. I loved working at a stupid flower shop or at the convenience store. I loved sitting in my room building lockpicks with my annoying talking cat, and trying my damnedest to eat a comically large hamburger. This game blew me away.

Persona 5 is on this list because never before have I cared so much about the lives of a group of high school kids before. Never before have I been so invested in a group of characters - never before have I felt so welcomed by a video game. The characters mattered to me. They had hopes and dreams, struggles and conflicts, and they captured my attention like games before never did. I needed to play to help them with chores. I needed to play to help them through difficult times in their lives, times of self doubt or fears of not fitting in. I needed to play to help them find their place in the world. I needed to play until I knew that all of the conflicts in their lives would be resolved and done. I was so involved in this virtual life and my role in all of it. It consumed my real life for a solid chunk of time until I had finished the game.

At one point during my play through I debated restarting entirely after romancing the wrong character. I only had one save slot and did not realize I had passed a point of no return until it was too late. It didn't concern me as the thought passed through my head that I had invested a hundred hours into the game by that point, because when my romance opportunity came for the actual character I wanted, I couldn't bear the thought of letting her down. Ultimately, I did not restart the game, so we would never know what it was like to be together. It was a difficult decision, but I think it was the right one to make. I still wonder though if one day I will revisit the game in New Game+ so that we may see each other again, my dear Makoto.


Oh and the music. Oh my god the music in this game.

 

Celeste

2018 (Played 2018)


One of the hardest games I've played, with a deep and thoughtful story hidden behind its brutal platforming. I think the character of Madeline is extremely well done. The game starts easy and ends hard. It is extremely satisfying in its execution and has tight controls and a great fluid pacing to it. The music is a master class in conveying emotions and really puts the bow on this complete package. What a game.

Hidden between the lines of this difficult pixel art platformer though, is the journey of a person attempting to find self acceptance and an ultimate feeling of belonging. You the player are there with her as she struggles with depression and anxiety. You are there as she tries and fails and tries again to climb Celeste mountain, even though everyone is telling her she should just go home. You are there as she battles the literal manifestation of her demons, as she wrestles with her gender identity. You are there as she grows to accept herself for who she is.

If you have ever heard anyone play Celeste, you would think it was the worst game in the world. You would see a player constantly die, over and over again at the exact same spot. You would hear swearing and complaining and the angry mashing of the A button because that has to be the problem. You would wonder why anyone even wants to play this game, let alone why, after so many failures, they would continue to insist on trying.

Celeste is on my list because I believe it is the game this decade that best exemplifies the idea that through struggles there is growth and through growth there is self-actualization. Looking back at my time with this game always makes me feel so good about myself. I think the most important thing about this game is that there are almost no in-game upgrades that change how Madeline plays at the beginning versus the end of the game. Madeline is, even at the end, just Madeline. It is my own accomplishments and achievements as the player that allowed me to complete the game. I did it. I faced the challenge, I got better, I succeeded. Though this mindset begins in the game, it is infectious and quickly pushed its way into the various other facets of my life. It is a mindset I continue to try to adhere to.

The game didn't give me invincibility, or the ability to fly, or faster running, or farther jumping, it only gave me some strawberries and a couple dashes. I pushed those buttons. Nobody was going to hand me that victory. I beat chapter one. I beat chapter seven. 

I got out of bed to beat depression, and the monotony of a life wasted by work. I looked inside myself to think about what mattered to me above all else, and I decided to make a change for the better.

I grew and achieved as a player. I got to the summit. I beat the Core B side. I beat the hotel C side. I struggled for those golden strawberries. I beat the final room of Farewell. I never gave up, even if I needed to take breaks - even if I never achieved everything (looking at you Moonberry).

I looked inside myself. I prioritized my life, I made time for the people that mattered to make the memories that will last. Even when the days were sad, even when the days were tough I never gave up. It's not perfect, it's never going to be perfect, but it's progress. With progress you only move up.

Madeline made it to the summit, but I climbed the mountain.


NieR: Automata

2017 (Played 2019)



The first game to make me emotional about a video game.

I'm not a crier when it comes to games and movies. Some are, that's fine. I'm not.

But this damn game, what a masterpiece. The most beautiful game that I will never. Play. Again. The closing section of the game is burned into my memories. Somewhere between 9S and A2 lying in a pool of each other's blood, 9S maniacally stabbing 2B over and over again since he has fallen in love with her, and 2B sexually straddling 9S's infected body as she chokes him to death. Somewhere in there. Maybe it's next to the part where you fight the CEO of Platinum Games in the coliseum, or stuffed in the back with Weight of the World looping on repeat. Hmmm.

This was the first game that got me to think beyond the game. Beyond just the feeling of entertainment slicing robots. Beyond the characters and how invested I was into desperately trying to have their stories end happily. Beyond the crushing difficulty which stopped me in my progression time and time again. Beyond the game itself as a disc in a box that starts when I turn the console on and ends when I power it off.

NieR: Automata needs at least 3 full play throughs before one can reach "the end", and each of those runs made me step more and more outside of my comfort zone. The first time through as 2B, I just hack and slash and kill enemies, because I'm a sexy robot girl and games are fun and I just want to progress to see what the ending is so I can move on to the next game. Some stuff happens in the story - it doesn't quite add up but eh, it's a sequel so maybe I missed something from the first game, whatever. I beat up some baddies, the game throws a plot twist on me about aliens but I don't really care because I had forgotten about the aliens anyway - I'm killing robots, not aliens remember? Then the game finishes - but not really - and I'm pretty satisfied with it. It lets me know I can play it again, but this time as 9S. Okay, cool.

But instead of the game starting with me in the flight suit shooting down baddies, I'm playing as a little robot. The game gives me this weak, defenseless character and makes me suffer for 5 minutes as I clumsily carry a bucket to my dying brother - not like it matters - he's dead. Here I start to notice that the game isn't quite as shallow as I once thought. Now that I'm here killing these robots again as 9S, and knowing what I know about the end of the game, I begin to disagree with 9S's methods and how he constantly dismisses the growing number of red-flags suggesting that my mission to save the world is not what it seems. The game doesn't care through, because it didn't make me play this second time. I chose to play like this. I killed these robots. They had brothers. They are defenseless and weak. I did this. I am the monster. The game says killing innocent robots didn't matter because it's just a game right? The game calls me out for sexualizing 2B and I feel bad because I think it's right. I'm still having fun, I guess?

Then I play a third time, because after I finished the second run the game plays a game preview trailer-like-thing for NieR: Automata enticing me to play once more. Yeah, the game plays an in game trailer for itself. What. Now it's a run through route C as A2 and things start to really get wacky.

The game changes my active character back and forth. 2B. 9S. A2. 9S. The game slowly unravels. The paint peels off the walls, the curtain is pulled back. This story has happened before. It will always happen again. 9S will discover terrible secrets about his organization and his role in furthering a twisted agenda. 9S will know too much, 2B will kill him, as she is ordered to. 2B will stifle her emotions to prevent herself from grieving over the loss of the only person she has ever emotionally opened up to. A2 will fight, but A2 has been fighting for nothing. The game makes a madman out of 9S. The game kills 2B. I kill innocent defenseless robots. Robots kill robots. I think I'm still having fun, but I want to go back to route A.

There's a fanatical religious robot cult that believes they all will become gods by dying. There's a group of robot children committing mass suicide because they have learned about the concept of fear and, now that they understand it, are not strong enough to confront it. I'm beginning to feel concerned, I'll even accept going back to route B at this point. I frantically begin searching for Wikipedia articles about various philosophers. I read Nietzsche.

The game asks me to kill my robot friend or wipe his memory so that he does not have to go on living knowing that he caused a group of robot children to commit suicide. I kill my friends and my previous comrades because the game tells me I need to in order to get to the end. My character breaks down and cries. His friends are dead. His existence is meaningless. I'm not having fun anymore. I regret everything. I am a broken shell of a person.

The game asks me to basically kill myself as its final boss, defeat 9S as A2, and defeat A2 as 9S - and ultimately the game asks me to kill the game itself by shooting the credits. I am killed by Square Enix and the credits at least 15 times before help arrives. Players who finished the game before me, come to assist me with messages of encouragement. It's not all for nothing. I can do anything I set my mind to. Games are not just meaningless time wasters. Finally I beat the credits. Finally I finish the game. Finally the game asks me to conclude the journey by voluntarily sacrificing my save data to help other players, just as they have helped me.

I sat and debated this for a long time. I wondered out loud about what I had done to get to this point. I recounted the steps on my journey and tried to dissect if there was anything I could have done different - or done better.

I have something like 100 hours in the game.

I click yes.

It asks me to confirm that I want to do this, to sacrifice my own hard work to help a stranger whom I've never met. A person who may never do the same for me. Or a person whom I may meet one day and dislike. Knowing all this, would I still be willing to sacrifice the things I've worked so hard to gain, for the benefit of one person who may never even know I did anything for them? Am I going to go through with this?

I do, of course, because at this point, I'm fully on board with NieR: Automata. I get it, or at least I get what I believe the game is trying to tell me. There is a sweet sense of serenity and completeness as I delete my save file forever. I sit on a special title screen and brood over the concept of humanity and existentialism.

If there's one game you needed to play this decade, I would say this was it.


Honorable Mentions

To the Moon (2011)

Mario Kart 8 (2014)

Counter Strike: Global Offensive (2012)

Uncharted 4: A Thief's End (2016)



At this point I've spent way too much time writing, so I'm going to end it here for now. Software stuff soon, maybe next week.



Stay tuned!

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================