DEV Community

Cover image for A simple Game Rule System using 0 for Success
Slobi
Slobi

Posted on

A simple Game Rule System using 0 for Success

Motivation

What the Rule System

Taking a task to recreate a simple game built with HTML and JavaScript into a production-ready game led me to re-evaluate its core components. Among them, the rule system, urging me to find an approach for clarity, flexibility, and maintainability. I'll weave through snippets of code and narrative, unraveling the creation of a game's rule system.

Why the messy structure

The conventional if this then that structure felt messy, prompting a search for an elegant and maintainable alternative.

One solution

How to maintainable alternative

By defining a set of message types using TypeScript enums, such as 'Ok,' 'NoFire,' and 'NoMove,' numeric values assigned to each type facilitated a streamlined evaluation process, laying the groundwork for a rule system, where 0 is OK, 0 is converted to false. That means that we can set our mental map to if not [OK, 0, false] there is an issue.

Mapping Rules for Simplicity

export enum ResultType {
    Ok = 0,
    NoFire,
    NoMove,
    NoFuel,
    FieldCantCreate,
    NoResources,
    NoBase,
    BaseNotInRange
}
Enter fullscreen mode Exit fullscreen mode

Knowing that the following code:

var result;
if(!false){
result = codeWillRun()
}
Enter fullscreen mode Exit fullscreen mode

can be written with logical operation OR:

var result = false || codeWillRun()
Enter fullscreen mode Exit fullscreen mode

as effectively the same, we can do a lot of designation making by adopting strategy to return 0 if all OK.

var result = rule1()||rule2()||...||ruleN()
if(result == 0){
    // all rules satisfied
}
Enter fullscreen mode Exit fullscreen mode

We can have as may rules as we want and it will all fall in its place with ease.

Example of create vehicle rules

Now a real thing, the rules for Vehicle Creation

export function canCreateVehicle(gameState: GameState, x: number, y: number, pl: number, vehType: number) {
    return canMapCreate(gameState, x, y) ||
        baseInRange(gameState, x, y, pl) ||
        isTooCloseToEnemy(gameState, x, y, pl) ||
        canAffordVehicle(gameState, pl, vehType);
}
Enter fullscreen mode Exit fullscreen mode

The 'canCreateVehicle' function exemplifies the power of chaining rules using the logical OR operator (||). It elegantly expresses the conditions for vehicle creation, with each rule encapsulated in its respective function, contributing to a more modular and comprehensible code-base.

Each rule follows a similar approach, maintaining a consistent structure that extends down to the fundamental building blocks of the game – primitive data types. This uniformity not only enhances the clarity and readability of the code but also facilitates a modular and scalable architecture. Whether examining the creation of vehicles, calculating distances, or ensuring result consistency, the underlying principles remain grounded in this structured and functional foundation.

Ensuring Result Consistency

export function isOk(res: ResultType) {
    return res === ResultType.Ok;
}
Enter fullscreen mode Exit fullscreen mode

To maintain result consistency, the 'isOk' helper function provides reminder that result should be OK. By centralizing the logic for checking if a result is 'Ok,' this function reduces redundancy, preventing potential misinterpretations, and contributing to the overall clarity of the code-base.

After-math

Drawbacks of using 0 for success

However, the practice of using 0 to represent success (Ok) and any other value to signify an error does have its downsides. Initially, it can be counter-intuitive, challenging developers to overcome ingrained expectations where 0 typically denotes failure. This unconventional approach may appear cryptic, particularly when using logical OR operators to chain rules. The reliance on the truthiness of non-zero values to indicate errors might lead to a mental mapping challenge, requiring developers to re-calibrate their understanding. While this approach enhances code organization and readability, it demands a shift in conventional thinking, necessitating a mental map to navigate the intricacies of rule evaluation.

In defence of using zero for positive

The decision to adopt the approach of using 0 to signify success and any other value as an error comes from a desire for a more informative rule system. In a binary system where 0 represents failure, each rule would convey a simple pass or fail status. However, this simplicity comes at the cost of losing granularity in error reporting. By designating various non-zero values to different error types, this approach allows for a more detailed understanding of rule failures.

Conclusion of a functional approach

In adopting a functional approach to game rule systems, the code-base underwent a renaissance, balancing simplicity and sophistication. Through Type Script Enums, logical operators, and nicely crafted functions, the rule system evolved into a consistent, explicit, and joyous entity. This approach is especially suited for turn-based games, not only addresses the initial implementation's shortcomings but paves the way for a more robust and enjoyable game making experience.

Top comments (0)