A simple Game Rule System using 0 for Success
Tags: gamedev, typescript, functional
Published: 2023-11-21 14:34:03

---
title: A simple Game Rule System using 0 for Success
published: true
description: Using 0 for success and any other value for error. This unconventional strategy allows for granular error reporting, offering transparency in understanding rule failures. 
tags: #gamedev #typescript #functional
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbhdvlzgduvvwad1fzkr.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-11-21 13:33 +0000
---
## 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
```typescript
export enum ResultType {
    Ok = 0,
    NoFire,
    NoMove,
    NoFuel,
    FieldCantCreate,
    NoResources,
    NoBase,
    BaseNotInRange
}
```

Knowing that the following code:
```typescript
var result;
if(!false){
result = codeWillRun()
}
```
can be written with logical operation OR:
```typescript
var result = false || codeWillRun()
```
as effectively the same, we can do a lot of designation making by adopting strategy to return 0 if all OK.
```typescript
var result = rule1()||rule2()||...||ruleN()
if(result == 0){
    // all rules satisfied
}
```
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
``` typescript
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);
}
```
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
```typescript
export function isOk(res: ResultType) {
    return res === ResultType.Ok;
}
```
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.