> The vast majority of essential code is not operating on just one object – it is actually implementing cross-cutting concerns. Example: when class Player hits() a class Monster, where exactly do we modify data? Monster's hp has to decrease by Player's attackPower, Player's xps increase by Monster's level if Monster got killed. Does it happen in Player.hits(Monster m) or Monster.isHitBy(Player p). What if there's a c…
> Player.hits(Monster).with(Weapon)
I think games are a particularly pathological case for OOP, and as such probably not a good example for the article's case. But FWIW, the problem with what you describe (and with OOP for games generally) is that game logic tends to be way too polymorphic for code like that. That is, players don't just Hit() monsters, they also hit items, traps, breakable terrain, etc., and they get hit by monsters, by projectiles, maybe explosions, fall damage, etc.
And the dilemma of doing all that in OOP is, you find yourself with 20 different things that can receive a Hit(), that have little in common otherwise. Some have hit points but others don't, some don't have an armor value, some need to receive knockback but others don't even have a physics body, etc. As a result, do you make Hit() accept 20 different types, with special cases for each? Or do you rejigger your class hierarchy so that those 20 classes all inherit from Hittable, etc? Either could work in this or that case, but neither's much fun.
This is all why ECS (or other aspect-based approaches) are so popular for games - they let you define very general "Hit(src, tgt)" chunks of logic, that don't care what type each object is, but can easily query whether or not they have hit points or a physics body.
But again, I think games are a pathological case here and none of this should necessarily be considered an argument against OOP generally.