HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

A simple "Tamagotchi" like game that uses math questions as food

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplequestionslikeusesgamethattamagotchimathfood

Problem

I wrote a Tamagotchi-like game where to feed the pet, you do math questions instead of just pressing a button. It's persistent, in that on loading, it figures out long it's been since the last save, and "simulates" the elapsed time. Hunger increases over time, and either causes the pet to heal, or become hurt, depending on if they're starving or not.

Pet.scala:

package pet

case class Pet(
        health: Float,
        hunger: Float,
        exp: Int,
        lastTime: Long
        ) {

    def setHealth(newHealth: Float): Pet = copy(
        health = newHealth
    )
    def setHunger(newHunger: Float): Pet = copy(
        hunger = newHunger
    )
    def setTime(newTime: Long): Pet = copy(
        lastTime = newTime
    )
    def setExp(newExp: Int): Pet = copy(
        exp = newExp
    )

    def healBy(by: Float): Pet = setHealth( (health + by) clampTo (0,100) )
    def hurtBy(by: Float): Pet = healBy(-by)

    def feed(food: Float): Pet = setHunger( (hunger - food) clampTo (0,100) ) 
    def starveBy(by: Float) : Pet = feed(-by)

    def addExp(newExp: Int): Pet = setExp(exp + newExp)
    def subExp(toSub: Int): Pet = addExp(-toSub)

    def isDead: Boolean = health = 100
    def isFull: Boolean = !isStarving

    override def toString: String = s"Pet (HP: ${health}, Hunger: ${hunger}, Exp: ${exp})"
}


Enviro.scala:

```
package pet

import java.io._
import scala.util.{ Try, Success, Failure }

case class Enviro(
pet: Pet,
settings: Settings
) {

def setPet(newPet: Pet): Enviro = copy(
pet = newPet
)

//Returns how long in seconds it's been since the last save
def getSecsElapsed: Long =
Time.getCurSecs - pet.lastTime

def checkSaveFile =
if (!settings.saveExists) save

def save =
Serialize(settings.savePath).write(pet)

def load: Enviro = setPet(
if (settings.saveExists) {
Try[Pet] (Serialize(settings.savePath).read.asInstanceOf[Pet])

Solution

Scala Style

-
You don't need the setters, just use copy directly.

-
You might be able to use pattern matching instead of if/else's in the utility methods in pet.scala.

-
For pet.hurtBy and pet.starveBy, return Option[Pet] where None means that the pet is dead. What you are currently doing is like you would do in Java where you remove the value and then checks if it is dead. But the Java way is basically wrong. You don't ever want a pet in a dead state. You are forced to handle death as soon as it happens.

-
Similarly, you should use Option when attempting to read the file.

Separation of Concern

-
Settings has some pet parameters and then a file name. Those two things do not belong together. (More comments on this later.)

-
You should cleanly separate the GUI logic from the "business logic". You do everything on the console at the moment, but you might move to a JavaFX GUI later. (Even if you don't change GUI, you should always code "as if" to keep a good separation of concerns.)
You don't want to have to modify any of your business classes when you change the GUI. For example, MathQuestion has a method questionLoop which does not belong in that class.

OO Design

I would have done the Pet, Settings and Enviro OO design differently.

I would put secondsPerStep andfoodPerQuestion in some general class (maybe called Settings) that is applicable to all pets. I would put hungerPerStep and starvePerStep as parameters with Pet. You might want to have variations in your pets and draw those values randomly for each pet.

I would not put those value in the same class as the file name. Also, you are persisting the file name which does not make much sense since if you ever change the file path in the main loop, the saved values would be wrong.

Naming

-
Settings should probably be named State. You are persisting the state.

-
generateQuestion instead of randomQuestion. Method names are usually verbs.

-
Serializer instead of Serialize. Class names are usually nouns.

-
type Op instead of type Ops. Type names are usually singular.

-
utils.scala instead of pet.scala since those are utility methods.

-
futureEnviro is really currentEnviro in Main.

UI

  • Instead of asking how many questions to ask at the start, maybe you could ask the user if she/he wants to continue after each question.


For example, answering 'q' to a question would stop immediately, without counting as a wrong answer.
After each question, you could then display the current pet state.

Others

-
Instead of your Time utilities, I would just stick with System.currentTimeMillis(). It returns the number of milliseconds since January 1, 1970 UTC as a long.
It might seem odd, but it is the default time format which is used nearly everywhere. I think it's better simply because it is more "standard".

-
I think there is too much file loading/saving in Main. I would only have one load attempt at the start (as an Option with a fallback pet creation method on None) and only one save after the main loop.

-
I don't like Enviro.load which sets the internal state of Enviro. It's not quite functional. It would be better to just have a method that returns an Option or Try of the persisted value. Also, I would only persist a tuple (pet, settings), where settings is itself just secondsPerStep andfoodPerQuestion, as discussed above. Even better: settings should actually just be hard coded values, or at least if they are persisted values, they should be persisted separately from the pets.

Context

StackExchange Code Review Q#75498, answer score: 2

Revisions (0)

No revisions yet.