0

I am trying to make a mini game where you input a command about what to do, and you need to input your since most of my inputs will look like this:atk 1 or:health 1

The input must in one line, but there are commands that don't have a number after them.

I need to input them in one line, so I use scanf("%s %d") to do this, but I also have some commands that don't have a number after the string, but if I don't use scanf I don't have ways to input them in a same line.

Is there solutions for situation like this? Sometimes the input is a string with a number, and sometimes a string only.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXSIZE 100

struct status
{
    int hp;
    char name[MAXSIZE];
};


int main()
{
    struct status you;
    struct status enemy;

    //your name and hp
    scanf("%s %d", you.name, &you.hp);
    //enemy's name and hp
    scanf("%s %d", enemy.name, &enemy.hp);

    char move[MAXSIZE];
    
    int damage = 0;

    int poison = 0;
    int round = 0;
    

    //game start
    //you start first, than you input what enemy do in next round
    //atk means attack, poison means you got poison by enemy on every end of rounds, health means you heal yourself, none means you skip this round
    //show means to show your and enemy's curreny status
    //end means end the game the process stop, it also stops when someone's hp is zero 
    while((you.hp > 0)&&(enemy.hp > 0) )
    {
        scanf("%s %d", move, &damage);

        if( strcmp(move,"atk") == 0 )
        {

            if((round%2) == 0)
                enemy.hp = enemy.hp - damage;
            else
                you.hp = you.hp = damage;

            round++;
        }
        else if( strcmp(move,"health") == 0 )
        {

            you.hp = you.hp + damage;

            round++;
        }
        else if( strcmp(move,"poison") == 0 )
        {

            poison = poison + damage;

            round++;
        }
        else if( strcmp(move,"none") == 0 )
        {
            round++;
        }
        else if( strcmp(move,"show") == 0)
        {
            printf("%s %d %s %d\n", you.name, you.hp, enemy.name, enemy.hp);
            continue;
        }
        else
            break;
    }
        if( strcmp(move, "atk") == 0)
        {
            if((round%2) == 0)
                printf("%s were defeated by attack", you.name);
            else
                printf("%s were defeated by your attack", enemy.name);
        }
        else if( strcmp(move, "end") == 0)
        {
            return 0;
        }
        else
            printf("%s were defeated by poison", you.name);

    return 0;
}
Oka
  • 23,367
  • 6
  • 42
  • 53
Jack C
  • 1
  • 1
  • Read the command first, and then in some `if` branches, read the number. – kotatsuyaki Dec 03 '22 at 15:35
  • Yes, there is. Stop using scanf for input, it was not meant to be used as such. Read a whole line with fgets and then parse it with sscanf, strtol, etc. You're also not checking the return value of scanf to see if it succeeded. The function also leaves a newline in the buffer which gets read by subsequent calls to other input functions, seemingly, another reason to avoid it. – Harith Dec 03 '22 at 15:45
  • Thanks, but turned out that other part of the process got problem, I'll avoid using scanf to read strings next time – Jack C Dec 03 '22 at 16:04

1 Answers1

0

The general advice is to avoid scanf.

Instead, fgets can be used to read an entire line of input from a stream into a buffer. Then the buffer can be freely manipulated, or parsed multiple times, without having to worry about the state of the stream.

The program below has examples of using both strtok (+ strtol) and sscanf to extract data from a string. Each of these tools has advantages and disadvantages; reading their documentation is recommended.

Some additional suggestions:

The use of an enum reduces the need to repeatedly compare strings, and its integral nature allows the use of a switch. An array kept in parallel (actions) to the enumeration type acts as a lookup table for string<->enum conversions. At the same time, this array can be used to indicate if a command requires an argument.

Placing your players in an array allows the use of modular arithmetic to determine the current player and next player without repeatedly checking the turn count. Alternatively, having a pointer to each player and swapping the values of the pointers each turn might prove easier to read.

poison should probably be an attribute of a player (struct status).

Here is an example program with a few of these concepts to play around with:

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXSIZE 100
#define SEP " \r\n"

struct status {
    char name[MAXSIZE];
    int hp;
    int poison;
};

static const struct {
    const char *method;
    bool needs_value;
} actions[] = {
    { NULL },
    { "atk", true },
    { "health", true },
    { "poison", true },
    { "show", false },
    { "end", false },
    { "none", false }
};

enum ACTION {
    ACTION_INVALID,
    ACTION_ATK,
    ACTION_HEALTH,
    ACTION_POISON,
    ACTION_SHOW,
    ACTION_END,
    ACTION_NONE,
    ACTION_ACTIONS
};

enum ACTION get_action(int *value)
{
    char buf[256];

    if (!fgets(buf, sizeof buf, stdin))
        exit(EXIT_FAILURE);

    char *method = strtok(buf, SEP);

    if (!method)
        return ACTION_INVALID;

    for (enum ACTION e = ACTION_ATK; e < ACTION_ACTIONS; e++) {
        if (0 == strcmp(method, actions[e].method)) {
            if (actions[e].needs_value) {
                char *value_token = strtok(NULL, SEP);

                if (!value_token || !*value_token)
                    break;

                char *end;
                errno = 0;
                *value = strtol(value_token, &end, 10);

                /* integer overflow or string was not completely parsed */
                if (ERANGE == errno || *end)
                    break;
            }

            return e;
        }
    }

    return ACTION_INVALID;
}

bool get_player(struct status *p)
{
    char buf[256];

    printf("Enter a name and hp: ");
    if (!fgets(buf, sizeof buf, stdin))
        exit(EXIT_FAILURE);

    p->poison = 0;

    return 2 == sscanf(buf, "%99s%d", p->name, &p->hp);
}

void print_status(struct status *s)
{
    printf("%s has %d HP and %d poison.\n",
            s->name, s->hp, s->poison);
}

int main(void)
{
    struct status players[2] = { { "Alice", 123, 0 }, { "Bob", 42, 0 } };

    /* uncomment for user input
    if (!get_player(players) || !get_player(players + 1))
        exit(EXIT_FAILURE);
    */

    int current = 0;

    while (1) {
        int next = (current + 1) % 2;
        int value;

        printf("%s's turn. Enter an action: ", players[current].name);

        switch (get_action(&value)) {
            case ACTION_INVALID:
                fprintf(stderr, "Invalid action.\n");
                continue;

            case ACTION_ATK:
                printf("%s deals %d damage to %s\n",
                        players[current].name, value, players[next].name);
                players[next].hp -= value;
                break;
            case ACTION_POISON:
                printf("%s applies %d poison to %s\n",
                        players[current].name, value, players[next].name);
                players[next].poison += value;
                break;
            case ACTION_HEALTH:
                printf("%s heals %d health.\n",
                        players[current].name, value);
                players[current].hp += value;
                break;

            case ACTION_SHOW:
                print_status(players + current);
                print_status(players + next);
                continue;
            case ACTION_END:
                return EXIT_SUCCESS;

            case ACTION_NONE:
                break;

            default:
                continue;
        }

        if (players[next].hp < 1) {
            printf("%s has slain %s!\n",
                    players[current].name, players[next].name);
            break;
        }

        /* apply poison, and reduce poison by one */
        if (players[current].poison)
            players[current].hp -= players[current].poison--;

        if (players[current].hp < 1) {
            printf("%s has died to poison!\n", players[current].name);
            break;
        }

        current = next;
    }
}

Example usage:

Alice's turn. Enter an action: show
Alice has 123 HP and 0 poison.
Bob has 42 HP and 0 poison.
Alice's turn. Enter an action: poison 22
Alice applies 22 poison to Bob
Bob's turn. Enter an action: atk 50
Bob deals 50 damage to Alice
Alice's turn. Enter an action: none
Bob's turn. Enter an action: atk 50
Bob deals 50 damage to Alice
Bob has died to poison!
Oka
  • 23,367
  • 6
  • 42
  • 53