Programming thread

How did you do that? That sounds really cool.
This was years ago so I don't remember all the details, but I'll share what I remember.

Simple games like these are essentially programs that run in a for loop and the entire logic is contained more or less within that loop. One iteration of said loop can also be called a "frame". I used the simplest ASCII art possible ("|" and "_" characters for drawing the field, "|" for the paddles, "*" for the ball). The logic of a single frame was something like this:
  1. Check for ball collision and handle the logic. If the ball is going to touch the paddle then invert the direction it is going into and decide whether it should go forward or diagonally depending on which part of the paddle was hit (not ideal, but whatever). If it hits the wall it should bounce. If it hits the goal the player should be awarded a point and the ball returned to to the middle where it will go in a random direction.
  2. Set the next position for the ball. The ball can either move to left, right and diagonally.
  3. Calculate the next position of the computer opponent. I made it follow the vertical position of the ball. I added some small random chance that the computer will do nothing or will go in the opposite direction in order to make it less "perfect", since without that it would be pretty much unbeatable.
  4. Check if the up arrow or down arrow are pressed. If true, move the player paddle to the next position.
  5. Draw the entire field, the paddles the ball and the scoreboard. This was quite inefficient, as it required redrawing the entire field (it should just draw the updates instead) every iteration of the loop.
  6. Go back to the beginning.
This logic does not include the initial setup, but it is not complicated (draw the screen, send the ball in some random direction).
The game itself was super slow and clunky to play due to constant redraws of the screen and because the Windows console is a pretty bad fit for this kind of stuff. There were a couple of other issues such as fixed screen size but I stopped caring so I never really solved any of these.
 
You can use curses for this to help with drawing the "field"/"board" and it should look nicer than a manual redraw using print() in Powershell or any decent Mac/Linux terminal. Termbox was also mentioned earlier but I'm not familiar with it.
 
  • Informative
Reactions: Belisarius Cawl
I'm getting sick of looking at this now, so here you go. A simplified text-mode Pong in Ruby, for Linux specifically because it directly parses /dev/input devices. (This means with some hacking, it'll use a joystick/gamepad just fine, or even a mouse (L/R buttons go up/down?) if you wanna get crazy.) No Curses, only Ruby's io/console. Game ends the first time you fail to return.

Set DevInput to the name of your keyboard device. Use "ls -l /dev/input/by-id" to find your keyboard. Uses scan codes, so set QuitScanCode, UpScanCode, and DownScanCode accordingly if they're not the same on your rig. By default: A goes up, Z goes down. Escape quits.

Pretends the field is a big square, so behaviour is anisotropic, but that's no big deal.

Code is ugly as sin because I got sick of refining it, but maybe you guys might have some fun here.

Ruby:
#!/usr/bin/env ruby
require 'io/console'
DevInput='/dev/input/event3' #keyboard dev
QuitScanCode=1   #Esc
UpScanCode=30    #A
DownScanCode=44  #Z

FPS=60
Step=1.0/FPS
Tau=2*3.1415927
TauDeg=Tau/360
Ns=10.0**9
PaddleRatio=10.0
PaddleSpeed=Step*50

$returns=0

def center_text msg, col
  mw=msg.length
  sw=STDOUT.winsize[1]
  STDOUT.goto col, sw/2-mw/2
  puts msg
end
def now
  Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
end
def halt val=0
  STDIN.iflush
  STDOUT.cooked!
  STDOUT.goto 0,0
  STDOUT.clear_screen
  puts "Score: #{$returns}"
  Kernel::exit(val)
end
def print_paddle rows,pos
  hc=pos.clamp(0.0,1.0)
  l=(rows*8/PaddleRatio).ceil
  ht=(rows*hc-1/PaddleRatio/2*l).clamp(0,rows)
  hb=(rows*hc+1/PaddleRatio/2*l).clamp(0,rows)
  if(ht.floor>0)
    STDOUT.goto ht.floor-1,0
    print " "
  end
  if(hb.ceil-1<rows)
    STDOUT.goto hb.ceil+1,0
    print " "
  end

  (ht.floor .. hb.ceil).each{|y|
    STDOUT.goto y,0
    print "#"
  }
end

process_winch=true
Signal.trap('SIGWINCH') do
  process_winch=true
end

start_time=now
frame=0
x,y,theta,vel=0,0.8,-20.0*TauDeg,1
wx,hy=0,0
up_t,dn_t=0,0
padh=0.5
frame_start=now-1.0/FPS

STDIN.raw!
f=File.open(DevInput)
while true do
  last_frame=frame_start
  frame_start=now
  would_block=false
  until would_block do
    begin
      d=f.read_nonblock(24)
    rescue IO::WaitReadable
      would_block=true
    end
    if d
      sec,usec,type,code,value=d.unpack("qqSSl")
      if(type==1) #Key
        case value # 30-a 44-z
        when 1 #KeyDown
          case code
          when QuitScanCode
            halt
          when UpScanCode
            up_t=sec*Ns+usec*1000
          when DownScanCode
            dn_t=sec*Ns+usec*1000
          end
        when 0 #KeyUp
          zz=((sec*Ns+usec*1000)-last_frame)/Ns*PaddleSpeed
          case code
          when UpScanCode
            padh-=zz
            up_t=0
          when DownScanCode
            padh+=zz
            dn_t=0
          end
        end
      end
    end
  end

  if(up_t>0 && up_t<frame_start)
    padh-=(frame_start-up_t)/Ns*PaddleSpeed
    up_t=frame_start
  end
  if(dn_t>0 && dn_t<frame_start)
    padh+=(frame_start-dn_t)/Ns*PaddleSpeed
    dn_t=frame_start
  end

  # recalc each frame because we don't know
  w=STDOUT.winsize[1]-1
  h=STDOUT.winsize[0]
  ew=1/w
  eh=1/h

  halt if(h<13)
  if process_winch
    STDOUT.clear_screen
    process_winch=false
  end

  STDOUT.goto hy,wx
  print "  "
  dx=Math.cos(theta)*vel*Step
  dy=Math.sin(theta)*vel*Step
  x+=dx
  y+=dy
  if(x>=(1-ew*2))
    x=1-ew*2
    theta=(100 + rand(1600)/10)*TauDeg
  elsif(x<ew)
    if((y-padh).abs<(1/PaddleRatio))
      x=2*ew
      theta=((y-padh)/(1/PaddleRatio/2)*66)*TauDeg
      $returns+=1
    else
      halt
    end
  end
  if(y>(1-eh))
    y=1-eh
    theta=-theta
  elsif(y<eh)
    y=eh
    theta=-theta
  end

  wx=(w-2)*x
  hy=h*y
  STDOUT.goto hy,wx
  print "()"

  padh=padh.clamp(0.0,1.0)
  print_paddle h,padh

  STDOUT.goto h-1,w-10
  desired_time=(start_time/Ns) + frame.to_f/FPS - (now/Ns)
  print "#{ "%1.1f" % ( (1.0/FPS) / ( 1.0/FPS - desired_time ) ) }x  "
  STDOUT.goto 0,w-20
  print "  Score: #{$returns}  "
  sleep desired_time if desired_time > 0
  frame+=1
end
halt
 
Last edited:
Termbox is pretty much always the way to go instead of ncurses these days. My chat client uses a TUI library that applies the termbox concepts nicely.
That sure does look better than NCurses, given that the interface is at least comprehensible and probably isn't buggy (I think they might have done something recently that they consider a fix, but its a 30 year old library ffs).
But, the optimization looks horrid; does it not have a problem with flashing?
It also misses what NCurses calls WINDOW-s.
I feel like they only resolved the easy parts.

And there is a termbox2, which looses the simplicity, is header-only -so everything is lumped together- and as we all know they consider documentation to suck. Amazin'.
Anyways, thanks, I might try it.
 
  • Like
Reactions: y a t s
does it not have a problem with flashing?
I haven't noticed any of that in my usage of it. Whatever the Go library I use does with the termbox concept is done in a quick enough way that I can justify redrawing the whole buffer to handle chat message edits.

I don't believe it uses C bindings or anything like that, but it has the same underlying mechanism as termbox. So there is clearly a way to implement it in a fast enough manner.
 
  • Like
Reactions: Marvin
has anyone tried ftxui? looks pretty good
I have, twice at least. I have a note on it here, all it says is:
meh on all fronts at best
Not sure what my actual problem was.
I also tried TermOx and NotCurses with similar results.

But before I come across as overly critical, I can actually easily describe what I would be happy with: "the raylib of the terminal".
 
I would say "try ncurses and make a TUI", but don't because NCurses is shit. I'm sure that there is some python TUI library that's fine, but I can't give you a recommendation on that front.
I just had a look at the python curses module and it looks like it's just a thin wrapper around the C interface. I would have expected something a little more user friendly from Python...

Curses could be so much nicer. Python has all of these "magic methods" for a reason. I'm pretty sure that something like this could be made to work.
Python:
import better_curses
# class handles init and clean up
main_window = better_curses.Window() # defaults; echo=False, cbreak=True, keypad=True
# no need for "ch" and "str" variants as python does not have chars
main_window.print("E") # can also accept x, y kwargs

Speaking of the gamba game, here's the updated version with all the sleeps, if anyone is interested.
The code in the try block should be limited to only what is actually being checked, it doesn't matter much here, but, if some other function in the block threw a ValueError then the user would get an unhelpful message about invalid input (and, in some cases, the program state may become corrupted). The while loop should also only contain what is necessary for handling the input. There is also repeated code at the start/end of both if/else branches that can be moved out (i.e. the code does not depend on the condition).
This makes the intent clearer and reduces the level of indentation required, which makes the code easier to read and understand (and maintain).

Python:
def gamba_game(game_state):
    # function vars 

    while True:
        print(f"you have ${game_state.cash}")
        bet = input("enter bet\n$")
        try:
            stake = int(bet)
        except ValueError:
            print("Invalid Input")
            continue

        if stake > game_state.cash:
            print("too poor")
        else:
            break

    # display roll animation

    if game_roll <= win_chance:
        # do win stuff
    else:
        # do lose stuff

    # print player balance
 
The code in the try block should be limited to only what is actually being checked, it doesn't matter much here, but, if some other function in the block threw a ValueError then the user would get an unhelpful message about invalid input (and, in some cases, the program state may become corrupted). The while loop should also only contain what is necessary for handling the input. There is also repeated code at the start/end of both if/else branches that can be moved out (i.e. the code does not depend on the condition).
This makes the intent clearer and reduces the level of indentation required, which makes the code easier to read and understand (and maintain).
Like this?
Python:
def gamba_game(game_state):
    
    game_roll = random.randint(1,100)
    win_chance = 49

    display_roll = 100 - game_roll

    roll_count = 1

    while True:

        bet = input("how much would you like to bet?: $")
        time.sleep(0.2)

        try:
            stake = int(bet)

            if stake > game_state.cash:
                print("You don't have that much money!")
                time.sleep(0.2)
            else:
                print(f"You bet ${stake}")
                break
        except ValueError:
            print("Invalid input")

    if game_roll <= win_chance:
        print("0",end="")
        while roll_count < display_roll + 1:
            print(f"\r{roll_count}", end="")
            time.sleep(0.02)
            roll_count = roll_count + 1
        print("\nYes!")
        time.sleep(0.2)
        game_state.wins = game_state.wins + 1
        game_state.cash = game_state.cash + (stake)
        print(f"You won ${stake}")
        time.sleep(0.2)
        print(f"your balance is ${game_state.cash}")
        time.sleep(0.2)
    else:
        print("0",end="")
        while roll_count < display_roll + 1:
            print(f"\r{roll_count}", end="")
            time.sleep(0.02)
            roll_count = roll_count + 1
        print("\nNo")
        game_state.losses = game_state.losses + 1
        game_state.cash = game_state.cash - stake
        print(f"You lost ${stake}")
        time.sleep(0.2)
        print(f"your balance is ${game_state.cash}")
        time.sleep(0.2)

Also I've been taking a break from this because I'm lazy, but I'm back on it now. Right now I'm working on the slots game. from the looks of it getting slots to work shouldn't be an issue. However, implementing it into the game may be a struggle. I want to make it so that you can switch between games at will. The main issue I see is that right now the gamba_sesh function only calls the dice/yesno game. should I just move gamba_sesh into gamba_game? Should I build slots its own gamba_sesh function? Or is there some other way to make this work that I'm not thinking of?
 
Alright, here's the updated game with slots.
Python:
import random

from colorama import Fore,Style

import time

class Gamestate:
    def __init__(self,wins=0,losses=0,cash=100):
        self.wins = wins
        self.losses = losses
        self.cash = cash

    @staticmethod
    def start_game():
        return Gamestate()
    
def gamba_slots(game_state):

    print('Enter "payout" into the bet line to view the payout list')
    time.sleep(0.2)
    
    payout_jackpot = "\U0001F95D \U0001F95D \U0001F95D | Bet x 300"
    payout_4 = Fore.RED + Style.BRIGHT + " 7  7  7 " + Style.RESET_ALL + "| Bet x 30"
    payout_3 = "\U0001F34B \U0001F34B \U0001F34B | Bet x 10"
    payout_2 = "\U0001F347 \U0001F347 \U0001F347 | Bet x 5"
    payout_1 = "\U0001F352 \U0001F352 \U0001F352 | Bet x 2"

    payout_list = [payout_jackpot,payout_4,payout_3,payout_2,payout_1]


    wheels = [[1,1,1,2,2,3,3,4,4,5], [1,1,1,2,2,3,3,4,4,5], [1,1,1,2,2,3,3,4,4,5]]

    icons = {"5" : "\U0001F95D",
         "4" : (Fore.RED + "\033[1m7\033[0m"),
         "3" : "\U0001F34B",
         "2" : "\U0001F347",
         "1" : "\U0001F352"}
    
    spin_result = ""

    display_result = ""

    
    while True:

        bet = input("how much would you like to bet?: $")
        time.sleep(0.2)
        
        if bet.lower() == "payout":
            for payout in payout_list:
                time.sleep(0.2)
                print(payout)
        else:

            try:
                stake = int(bet)

                if stake > game_state.cash:
                    print("You don't have that much money!")
                    time.sleep(0.2)
                else:
                    print(f"You bet ${stake}")
                    break
            except ValueError:
                print("Invalid input")

    game_state.cash = game_state.cash - stake

    for wheel in wheels:
        number = str(random.choice(wheel))
        spin_result += number
        display_result += icons[number] + " "
        print(f"\r{display_result}", end = " ")
        time.sleep(0.5)

    print("")

    if spin_result == "555":
        print("You won the jackpot!")
        time.sleep(0.02)
        game_state.cash = game_state.cash + (stake * 300)
        print(f"You won ${stake * 300}")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)
    elif spin_result == "444":
        game_state.cash = game_state.cash + (stake * 30)
        print(f"You won ${stake * 30}")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)
    elif spin_result == "333":
        game_state.cash = game_state.cash + (stake * 10)
        print(f"You won ${stake * 10}")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)
    elif spin_result == "222":
        game_state.cash = game_state.cash + (stake * 5)
        print(f"You won ${stake * 5}")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)
    elif spin_result == "111":
        game_state.cash = game_state.cash + (stake * 2)
        print(f"You won ${stake * 2}")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)
    else:
        print("Dude, this shit is so rigged")
        time.sleep(0.02)
        print(f"Your balance is ${game_state.cash}")
        time.sleep(0.02)

def gamba_game(game_state):
    
    game_roll = random.randint(1,100)
    win_chance = 49

    display_roll = 100 - game_roll

    roll_count = 1

    while True:

        bet = input("how much would you like to bet?: $")
        time.sleep(0.2)

        try:
            stake = int(bet)

            if stake > game_state.cash:
                print("You don't have that much money!")
                time.sleep(0.2)
            else:
                print(f"You bet ${stake}")
                break
            
        except ValueError:
            print("Invalid input")

    if game_roll <= win_chance:
        print("0",end="")
        while roll_count < display_roll + 1:
            print(f"\r{roll_count}", end="")
            time.sleep(0.02)
            roll_count = roll_count + 1
        print("\nYes!")
        time.sleep(0.2)
        game_state.wins = game_state.wins + 1
        game_state.cash = game_state.cash + (stake)
        print(f"You won ${stake}")
        time.sleep(0.2)
        print(f"your balance is ${game_state.cash}")
        time.sleep(0.2)
    else:
        print("0",end="")
        while roll_count < display_roll + 1:
            print(f"\r{roll_count}", end="")
            time.sleep(0.02)
            roll_count = roll_count + 1
        print("\nNo")
        game_state.losses = game_state.losses + 1
        game_state.cash = game_state.cash - stake
        print(f"You lost ${stake}")
        time.sleep(0.2)
        print(f"your balance is ${game_state.cash}")
        time.sleep(0.2)

def gamba_start(game_state):
    
    print(f"Welcome to the casino!")
    time.sleep(0.2)
    print(f"You have ${game_state.cash}.")
    time.sleep(0.2)

    while True:
        play =  input(f"Would you like to play?(yes/no): ")
        time.sleep(0.2)
        
        play = play.lower()

        match play:
            case "yes"|"y":
                gamba_sesh(game_state)
                break
            case "no"|"n":
                print("That's probably a good idea")
                break
            case _:
                print("Invalid input, please try again")

def gamba_sesh(game_state):

    while True:
        
        if game_state.cash <= 0:
            print("Dude I just lost it all")
            time.sleep(0.2)
            print(f"wins {game_state.wins}")
            time.sleep(0.2)
            print(f"losses {game_state.losses}")
            break
        else:

            print("The games available are slots and yesno (more coming soon!)")
            time.sleep(0.2)
            print("To play slots enter (s), for yesno enter (y) and to leave the casino enter (q)")
            time.sleep(0.2)

            play = input("What would you like to do?: ")
            time.sleep(0.2)

            play = play.lower()
            
            match play:
                case "s"|"(s)":
                    gamba_slots_play_again(game_state)
                case "y"|"(y)":
                    gamba_game_play_again(game_state)
                case _:
                    print("One more game of dice and I'm out!")
                    time.sleep(0.2)
                    gamba_game(game_state)

def gamba_slots_play_again(game_state):
    
    gamba_slots(game_state)

    while True:
        
        if game_state.cash <= 0:
            break
        else:
            play_again = input("Play again? (yes/no): ")
            time.sleep(0.2)

            play_again = play_again.lower()
            
            match play_again:
                case "yes"|"y":
                    gamba_slots(game_state)
                case "no"|"n":
                    print("Slots exited")
                    time.sleep(0.2)
                    break
                case _:
                    print("Invalid Input")
                    time.sleep(0.2)

def gamba_game_play_again(game_state):
    
    gamba_game(game_state)

    while True:
        
        if game_state.cash <= 0:
            break
        else:
            play_again = input("Play again? (yes/no): ")
            time.sleep(0.2)

            play_again = play_again.lower()
            
            match play_again:
                case "yes"|"y":
                    gamba_game(game_state)
                case "no"|"n":
                    print("Dice exited")
                    time.sleep(0.2)
                    break
                case _:
                    print("Invalid Input")
                    time.sleep(0.2)

game_state_start = Gamestate.start_game()
gamba_start(game_state_start)

I feel like the method for getting the payout list and the payout list itself are kinda clunky. Everything seems to be working, but my eyes might be missing something. I'm pretty excited about this, from here it should be rather easy to implement other games.

Also I want to say again thank you guys so much for the help. You all have helped to make this experience fun and informative.
 
Did Terry have any unironically good programing advice?
Complexity in a system's design for complexity's sake is niggerlicious. Everything has a cost, if your reasoning for something is for the complexity, you're spending computational resources and effort without gaining anything from that, not even anything abstract like generally applicable technical knowledge.
C/C++ otherwise
C and C++ are distinct and different programming languages, there is no such thing as C/C++. Sure you can do C-isms in C++, but if you're using a C++ compiler, you're using C++ even if you manually call malloc and free. Yes this is an autistic distinction, but it is a very important one because of C's looser and more implicit type system.
How will I know that I'm comfortable enough?
The real answer is that if doing it in python feels good and keeps you enthusiastic, keep doing it in python. Push in the direction that serves you, not some chuds on a forum. Whatever keeps you on the path moving towards whatever your goal was with picking up programming.
here's the updated game
Not saying you should post it and accidentally phonebook yourself in the commit logs, but are you using version control software like git? If you aren't, it is very worthwhile learning even if you never push to a remote repository somewhere. It means you can always revert to a previous working code state so long as you committed it.
 
Alright, here's the updated game with slots.
Use more functions to reduce the number of repeated fragments.
For example:
you have a lot of prints that are followed by a sleep, always the same sleep.
Python:
def _print_sleep(text: str, sleep_seconds=0.02):
    print(text)
    time.sleep(sleep_seconds)
And your play again function:
Python:
import typing

def play_again(game_run: typing.Callable, name: str, game_state: Gamestate):
 
    game_run(game_state)

    while True:
    
        if game_state.cash <= 0:
            break
        else:
            play_again = input("Play again? (yes/no): ")
            time.sleep(0.2)

            play_again = play_again.lower()
        
            match play_again:
                case "yes"|"y":
                    game_run(game_state)
                case "no"|"n":
                    _print_sleep(f"{name} exited")
                    break
                case _:
                    _print_sleep("Invalid Input")
Call it with
Python:
play_again(gamba_slots, "Slots", game_state)
There's a convoluted way to add a name to the function so it doesn't have to be passed separately, but it looks like sorcery.
Python:
def func_name(name):
    def wrapper(fn):
        fn.name = name
        return fn
    return wrapper
then
Python:
@func_name("Slots")
def slots(game_state):
    ...
and eventually
Python:
_print_sleep(f"{game_run.name} exited")

The massive spin block can be improved. Make a dictionary of spin result to wins, get the result from there, now depending on the result you have three possiblities:
jackpot (needs an extra message but otherwise like a regular win)
win but no jackpot
loss
Python:
JACKPOT = 300
WINNING_SPINS = {
    "555": JACKPOT,
    "444": 30,
    # rest
}

win_factor = WINNING_SPINS.get(spin_result, -1)
winnings = stake * win_factor

# display if jackpot
if win_factor == JACKPOT:
    _print_sleep("You won the jackpot!")

if win_factor > 0:
    _print_sleep(f"You won {winnings}")
else:
    _print_sleep("Dude this shit is so rigged")
 
game_state.cash += winnings
_print_sleep(f"Your balance: {game_state.cash}")

Also, don't name everything "gamba-something", instead, name the file "gamba.py". Function names should ideally have distinct names, so that the code is easier to read, and so that when you're using autocomplete, the first couple of typed characters are enough for autocomplete to get the correct function.

Then, when you're calling gamba from somewhere else, you can import gamba and refer to functions in the gamba file with the dot notation
Python:
import gamba

def do_something(*a, **kw):
    gamba.slots( ...... )

Alternatively, you can make a gamba directory within the project (or just use your project's top directory if this project is all about gambling), put a blank __init__.py file in it, and put each game into a separate file in the directory.

Not saying you should post it and accidentally phonebook yourself in the commit logs, but are you using version control software like git? If you aren't, it is very worthwhile learning even if you never push to a remote repository somewhere. It means you can always revert to a previous working code state so long as you committed it.
Another thing to learn is virtual environments. @Cranjis McBasketball even if you're currently using the system python (what's your OS?), eventually you will have separate projects and separate sets of libraries in each with their specific versions. A library you want to use can be dependent on more libraries it wants to use, these can change from version to version, each of those have versions, too. There are ways to organize these sets and describe each set to allow necessary updates and disallow potentially breaking updates. I use poetry, which is complicated and gay, but a good alternative for beginners is venv (built-in) + pip tools https://github.com/jazzband/pip-tools .
 
Last edited:
C and C++ are distinct and different programming languages, there is no such thing as C/C++. Sure you can do C-isms in C++, but if you're using a C++ compiler, you're using C++ even if you manually call malloc and free. Yes this is an autistic distinction, but it is a very important one because of C's looser and more implicit type system.
I recommend people learn C or C++ as their first language. I wasn't lumping them in as one thing.
 
Then probably C, since the fundamentals are the same, but there is simply less stuff to get confused by.
For example, I remember being baffled by smart pointers because they were presented to me before having a firm grasp on raw pointers.

However, it helps no one to be reductive.

You know distrochooser? Apparently there is no langchooser.
I did find some bad attempts tho.
This one just shills Rust.
This and this one are just plain bad and buggy. They were clearly made by junior webdevs, can't be mad at them.
And this one is like the above two, except I find my result funny.
Screenshot_2025-05-30_16-48-35.webp
 
Last edited:
  • Like
Reactions: ADHD Mate
Then probably C, since the fundamentals are the same, but there is simply less stuff to get confused by.
For example, I remember being baffled by smart pointers because they were presented to me before having a firm grasp on raw pointers.
That's my mine gripe with modern C++ crowd. C++ is a mess, but a lost of things start to make some sense if you go bottom up in design, starting with C, then RAII. and only after that you go for modern features.
But that's an issue with modern IT in general, where everyone just wants/or push the newest shiniest toys. Where old fundamental methods, in many cases often as capable for things you want to do when you learn, are discouraged cuz "muh bad practices".
And we end up with abstractions atop abstractions.

@Cranjis McBasketball
C is imho the best choice, as most of other languages would use/interact with C one way or another. But it does not matter. If you like Python that's good enough reason to stick to it.

Also my tip is to use the least amount of libraries you can get away with. Like instead of using ncurses/other just print escape characters. Maybe create your subroutines for drawing what you want using them, just giving coordinates or so.
Will it be inflexible? Yes. Will it be portable? No. Might it require manualy chosen terminal size? Sure.
But at this point if it works on your machine, it is good enough. And you might develop sense when it's good to use library, and when it's okay to roll your own solution.

Often to really understand good practices, you need to the bad way yourself and get the issues that come with it.
 
Back