C Code Converted to FreeBASIC with C run time Library example

Started by stigma, Jan 18, 2023, 03:34 PM

Previous topic - Next topic

stigma

ok so first I'm learning C Programming Language and Coded or Ported Ron77 console game "Homeless - surviving the streets" to kinda simple C program (without string arrays yet)

here is the code in C :

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <String.h>
#include <time.h>


#define BEG1 "you have been begging all day for food and/or money but nothing"
#define BEG2 "you succeeded in getting some food and money from kind people"

#define WORK1 "you tried to find work but you where rejected everywhere you went"
#define WORK2 "you finded work and after 2 weeks got your frist sallery! you are off the streets! yay!"

#define PHONE1 "you phone family or friends you used to know but no one answers"
#define PHONE2 "you phone your parents - they come and get you and you are safe at home - you are off the streets - yay!"

#define DRUGS1 "you buy cheap alchohol and booze to try and forget your toubles pass out and wake up at hospital"
#define DRUGS2 "you get OD from bad alchohol and drugs and you die on the streets - RIP :( GAME OVER"

#define GAMEOVER "you die on the streets from hunger and desise and fetige - RIP"

int days = 1;
int health = 50;
int money = 55;
// bool isGameOver = false;

int message( char msg1[], char msg2[])
{
	time_t t;
	srand((unsigned) time(&t));
	int randonNumber = rand() % 2;
	int r = 0;
	if (randonNumber == 0)
	{
		printf("\n%s", msg1);
		r = 1;
	}
	else if (randonNumber == 1)
	{
		printf("\n%s", msg2);
		r = 2;
	}
	return r;
}

void opening(int health, int money, int days )
{
	printf("\nyou are on the streets for %i days\nyou have %i health: %i money", days, health, money);
}

int main()
{
	
	int input =0;
	int result = 0;
	do
	{
		system("cls");
		opening(health,money,days);
		
		printf("\nchoose what to do: \n1. beg for money or food\n2. try to find work or a job\n3. phone someone for help\n4. buy some street drugs/alcohol to forget your troubles\n");
		
		scanf("%d", &input);
		
		if (input == 1)
		{
			result = message(BEG1, BEG2);
		}
		else if (input == 2)
		{
			result = message(WORK1, WORK2);
		}
		else if (input == 3)
		{
			result = message(PHONE1, PHONE2);
		}
		else if (input == 4)
		{
			result = message(DRUGS1, DRUGS2);
		}
		
		if (input == 1 && result == 2)
		{
			health += 25;
			money += 30;
			days += 1;
		}
		else if (input == 2 && result == 2)
		{
			// isGameOver = true;
			break;
		}
		else if (input == 3 && result == 2)
		{
			// isGameOver = true;
			break;
		}
		else if (input == 4 && result == 2)
		{
			// isGameOver = true;
			break;
		}
		else
		{
			days += 1;
			health -= 5;
			money -= 10;
		}
		
		getchar();
		getchar();
	} while (health >= 0);
	getchar();
	getchar();
	getchar();
	return 0;
}

now I was wondering how or if I could convert this code into FreeBASIC using the C Run-Time Library

so here is the exact code ported to FreeBASIC using the C runtime Librarie (include "crt.bi"):

#include "crt.bi"

#define BEG1 "you have been begging all day for food and/or money but nothing"
#define BEG2 "you succeeded in getting some food and money from kind people"

#define WORK1 "you tried to find work but you where rejected everywhere you went"
#define WORK2 "you finded work and after 2 weeks got your frist sallery! you are off the streets! yay!"

#define PHONE1 "you phone family or friends you used to know but no one answers"
#define PHONE2 "you phone your parents - they come and get you and you are safe at home - you are off the streets - yay!"

#define DRUGS1 "you buy cheap alchohol and booze to try and forget your toubles pass out and wake up at hospital"
#define DRUGS2 "you get OD from bad alchohol and drugs and you die on the streets - RIP :( GAME OVER"

#define GAMEOVER "you die on the streets from hunger and desise and fetige - RIP"

dim shared as INTEGER days = 1
dim shared as INTeger health = 50
dim shared as integer money = 55

function message( msg1 as zstring, msg2 as zstring) as INTEGER
	randomize()
	dim randomNumber as integer = int(rnd*2)
	dim r as INTEGER
	
	if randomNumber = 0 then
		printf(!"%s", msg1)
		r = 1
	elseif randomNumber = 1 then
		printf(!"%s", msg2)
		r = 2
	EndIf
	return r
End Function

sub opening(health as integer, money as integer, days as integer)
	printf(!"\nyou are on the streets for %i days\nyou have %i health: %i money", days, health, money)
End Sub


function main_game() as integer
	dim as integer inpt, result
	
	WHILE health >= 0
		cls
		opening(health, money,days)
		
		printf(!"\nchoose what to do: \n1. beg for money or food\n2. try to find work or a job\n3. phone someone for help\n4. buy some street drugs/alcohol to forget your troubles\n")
		
		scanf("%d", @inpt)
		
		if inpt = 1 then
			result = message(BEG1,BEG2)
		elseif inpt = 2 then
			result = message(WORK1,WORK2)
		elseif inpt = 3 then
			result = message(PHONE1, PHONE2)
		elseif inpt = 4 then
			result = message(DRUGS1, DRUGS2)
		EndIf
		
		if (inpt = 1 and result = 2) then
			health += 25
			money += 30
			days +=1
		elseif (inpt = 2 and result = 2) then
			exit WHILE
		elseif (inpt = 3 and result = 2) then
			exit WHILE
		elseif (inpt = 4 and result = 2) then
			exit WHILE
		else
			days +=1
			health -= 5
			money -= 10
		EndIf
		
		SLEEP
		
	WEND
	
	return 0
End Function

main_game()

printf(!"\nTHAT'S ALL FOLKS!")
sleep

hope this helps someone...

__blackjack__

The standard C header is named `string.h`, not `String.h`.  This may compile on Windows, but not on any system that where file names are case sensitive.  Also the `cls` command is windows specific.  And the header files `string.h` and `stdbool.h` are not needed here.

The random number generator should only be seeded once, not each time before a random number is generated.  The same remarks from FreeBASIC's RANDOMIZE documentation apply here.

Like it is at the moment it would be even possible to get the same "random" number each time.  It depends on the value of `t` which is undefined.  It could be any garbage data that's currently on the stack.  Which includes the possibility of the same values at each call.  Or even zero bytes each time if the compiler chooses to zero fill the locals.  That's not guaranteed but also not forbidden either by the C standard.  Usually, and also in FreeBASIC, the current time is used to seed the random number generator.  That would be passing a NULL pointer to `time()` in C here.

I know it is ported, so this may come from the original, but line end characters should not be printed at the start of the line but at the end.  This would be strange in BASIC too, to start each and every PRINT with a CHR$(10) and end the command in a `;` instead of using the fact that PRINT by default outputs a line end character (sequence) if not suppressed via `;`.  The messages might be printed with `puts()` instead of `printf()` in `message()`.  `puts()` puts a line ending by itself, like PRINT in BASIC.

Those multiple `getchar()` seem to be necessary because the `scanf()` pattern doesn't account for trailing whitespace characters, including the new line character from the input (or an EOF condition).  After using `scanf()` one must at least clear the input buffer up the line end or EOF.  Then there still would be inputs that start with a valid number but have trailing garbage and the code would accept this by simply ignoring the trailing garbage.  BASICs INPUT is so much more convenient.

The user input is not checked wether it is in range 1 to 4.  The code just continues with the last value of `i`, even it is not from the current user input.

`days`, `health`, and `money` should be local to the `main()` function.  Code and normal declarations of variables outside a FUNCTION or SUB in FreeBASIC is in the `main()` function in C.

The outer main loop would better be a `while` loop because then it would still work as expected if the `health` value is defined as negative before the loop.

`opening()` doesn't really warrant its own function.

The first evaluation of `input` with an `if` for each of the four cases would be a `switch` in C instead (and a SELECT CASE in FreeBASIC):
        switch (input)
        {
            case 1: result = message(BEG1, BEG2); break;
            case 2: result = message(WORK1, WORK2); break;
            case 3: result = message(PHONE1, PHONE2); break;
            case 4: result = message(DRUGS1, DRUGS2); break;
        }
But the code in each case is so regular and depends on a number that could be used as an index, that it would be much shorter to put all the messages into a two dimensional array:
static const char *MESSAGES[4][2] = {
    {BEG1, BEG2}, {WORK1, WORK2}, {PHONE1, PHONE2}, {DRUGS1, DRUGS2}
};

    ...

        result = message(MESSAGES[input - 1][0], MESSAGES[input - 1][1]);
Or if `message()` is changed to accept a pointer to a two dimensional array instead of two arguments it is just:
        result = message(MESSAGES[input - 1]);
Which also would massively simplify `message()` because the random number there could be used as index into that argument.  And the return value is the random number plus one.  So there is no `if`/`else` neccessary anymore:
int message(const char *messages[2])
{
    int randon_number = rand() % 2;
    puts(messages[randon_number]);
    return randon_number + 1;
}

But why must this return 1 and 2 instead of 0 and 1?  Then it is so simple that the function can be inlined, because then random number *is* the result.

The evaluation of `input` and `ouput` has several branches with the same code (`break`) and all not branches not leaving the loop have the increment of `days` in common.

So we end up here:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define BEG1 "you have been begging all day for food and/or money but nothing"
#define BEG2 "you succeeded in getting some food and money from kind people"

#define WORK1 "you tried to find work but you where rejected everywhere you went"
#define WORK2 "you found work and after 2 weeks got your frist salery! you are off the streets! yay!"

#define PHONE1 "you phone family or friends you used to know but no one answers"
#define PHONE2 "you phone your parents - they come and get you and you are safe at home - you are off the streets - yay!"

#define DRUGS1 "you buy cheap alchohol and booze to try and forget your troubles, pass out and wake up at hospital"
#define DRUGS2 "you get OD from bad alchohol and drugs and you die on the streets - RIP :( GAME OVER"


static const char * const MESSAGES[][2] = {
    {BEG1, BEG2}, {WORK1, WORK2}, {PHONE1, PHONE2}, {DRUGS1, DRUGS2}
};


void skip_to_end_of_line(void)
{
    char ch;
    while ((ch = getchar()) != '\n' && ch != EOF);
}


int main()
{
    int days = 1, health = 50, money = 55;
    int i, input, result;
    
    srand((unsigned int) time(NULL));
    while (health >= 0)
    {
        printf(
            "\nyou are on the streets for %i days\n"
            "you have %i health, %i money\n",
            days, health, money
        );
        puts(
            "choose what to do:\n"
            "1. beg for money or food\n"
            "2. try to find work or a job\n"
            "3. phone someone for help\n"
            "4. buy some street drugs/alcohol to forget your troubles"
        );
        /*
        * TODO This still accepts input like "3some stuff thats not digits" by
        *      simply ignoring the part where the digits stop up to the end of
        *      the line.
        */
        for (;;)
        {
            i = scanf("%d", &input);
            skip_to_end_of_line();
            if (i == 1 && input >=1 && input <= 4)
            {
                break;
            }
            puts("Error! Wrong input. Try again.");
        }

        result = rand() % 2;
        puts(MESSAGES[input - 1][result]);

        if (result == 1)
        {
            if (input != 1)
            {
                break;
            }
            health += 25;
            money += 30;
        }
        else
        {
            health -= 5;
            money -= 10;
        }
        days++;
        skip_to_end_of_line();
    }
    skip_to_end_of_line();
    return 0;
}

And that ported back to FreeBASIC should not need the C runtime.  Porting a C program to FreeBASIC should not be 1:1 to end up with a C program just disguised as FreeBASIC but with an idiomatic FreeBASIC program.  This is true of almost all ports from language A to language B, unless really only the Syntax is different and not also the idioms.

Here is my attempt of a back port:
Randomize Timer

Dim Messages(..., ...) As Const String = { _
    {"you have been begging all day for food and/or money but nothing", _
     "you succeeded in getting some food and money from kind people"}, _
    {"you tried to find work but you where rejected everywhere you went", _
     "you found work and after 2 weeks got your frist salery! you are off the streets! yay!"}, _
    {"you phone family or friends you used to know but no one answers", _
     "you phone your parents - they come and get you and you are safe at home - you are off the streets - yay!"}, _
    {"you buy cheap alchohol and booze to try and forget your troubles, pass out and wake up at hospital", _
     "you get OD from bad alchohol and drugs and you die on the streets - RIP :( GAME OVER"}}

Dim days As Integer = 1, health As Integer = 50, money As Integer = 55
Dim answer As Integer, result As Integer

Do While health >= 0
    Print
    Print "you are on the streets for"; days; " days"
    Print "you have"; health; " health,"; money; " money"
    
    Print "choose what to do:"
    Print "1. beg for money or food"
    Print "2. try to find work or a job"
    Print "3. phone someone for help"
    Print "4. buy some street drugs/alcohol to forget your troubles"
    Do
        Input answer
        If answer >= 1 And answer <= 4 Then Exit Do
        Print "Error! Wrong input. Try again."
    Loop
    
    result = Int(Rnd * 2)
    Print Messages(answer - 1, result)
    
    If result = 1 Then
        If answer <> 1 Then Exit Do
        health += 25: money -= 30
    Else
        health -= 5: money -= 10
    End If
    days += 1
    GetKey
Loop
GetKey
Two is the oddest prime.