Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
ObsidianBeast
Jan 17, 2008

SKA SUCKS

Hadlock posted:

If I want a dead simple, easy to use cross-platform 2d graphics api (not full screen) what is my go-to these days? The only other thing I've used so far is tkinter and it always felt really clunky. I guess there's QT but I haven't revisited that in ages

Basically I want a windowed "app" with some text down the left side with readouts of various metrics, maybe 1 or 2 graphs, and on the right is a big square with i guess 20 balls sprites bouncing around inside, off the walls as a sort of visualization of the "simulation" :airquote: I don't think I'd ever have more than 100 sprites on the screen

This might be heresy in the Python thread, but as someone who likes python and hates javascript, I had a good time using Godot for 2D games. The scripting language is very python-like and easy to get the hang of if you are used to python.

Adbot
ADBOT LOVES YOU

Hadlock
Nov 9, 2004

Is Godot support for python sort of like how unity offers c# / .net scripting support? I did a quick Google search, looks like it's written in c++ but most of the python questions are "is python actually supported?" dating to 2019

cinci zoo sniper
Mar 15, 2013




Hadlock posted:

Is Godot support for python sort of like how unity offers c# / .net scripting support? I did a quick Google search, looks like it's written in c++ but most of the python questions are "is python actually supported?" dating to 2019

It doesn’t support Python, it just offers a scripting language that syntactically is a carbon copy of Python in many respects. A lot of people end up thinking it’s actually Python, for whatever reason.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
I'm looking to make a representation of a gamebook in Python, being familiar with Lua. In Lua, I'd define a game state, a table containing the hero's current stats, inventory, and so forth, as well as the page the reader is currently on. I'd then want to have a function that would take a game state and return a sequence of game states that can be reached with a single decision from that game state. To do so, I'd have a handful of helper functions; for example, freechoice(g, possiblepages) would take the gamestate g and return a sequence of game states, each one with the player having turned to a different page in the sequence possiblepages. Then I'd define this:
code:
local pages = {
	[1] = function(g) return freechoice(g, {2, 3}) end,
	[2] = function(g) return randomchoice(g, {4, 5}) end,
	[3] = function(g) return hasitem(g, "Flask of Wine", {8, 9}) end,
	...
}

local nowturnto = function(g) return pages[g.page](g) end
Am I right in thinking I could do something like this in Python with:
code:
pages = {
	1: lambda g: freechoice(g, [2, 3]),
	2: lambda g: randomchoice(g, [4, 5]),
	3: lambda g: hasitem(g, 'Flask of Wine', [8, 9]),
	...

def nowturnto(g):
	return pages[g['page']](g)
If not, how would I do something like this in Python? Should I do something like this in Python? Is there a more Pythonic way?

e: corrected code.

FredMSloniker fucked around with this message at 05:05 on Nov 12, 2021

duck monster
Dec 15, 2004

Ranzear posted:

Just found out dictionaries are now ordered in 3.7+, but they didn't add any native way to sort by key. That's the most pythonian thing ever.

I think they've actually been ordered for quite some time, but it wasn't official. I remember having pretty rowdy arguments over this fact in an old job. With my take being "If its not official, dont count on it, the underlying implementation might change." cos I certainly remember falling into that trap in a much early python (I've been using python since the 90s) and discovering that no, it was not.

duck monster
Dec 15, 2004

cinci zoo sniper posted:

It doesn’t support Python, it just offers a scripting language that syntactically is a carbon copy of Python in many respects. A lot of people end up thinking it’s actually Python, for whatever reason.

Yeah its *definately* not Python. That said, its pretty easy to write it if you know python. Its not like "Boo" or whatever unities pseudo-python was called. It seems to think like a proper python, except, well its not, and in some very major ways.

I do enjoy GDScript though.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
I haven't seen any response to my earlier post. Was this the wrong place to ask? Is there a better place?

cinci zoo sniper
Mar 15, 2013




It’s not clear what you are doing or why, so chances are no one has anything meaningful to reply.

OnceIWasAnOstrich
Jul 22, 2006

Yeah I can't really tell what is happening (do all of those functions return a modified game-state?) but I can definitely answer that that doesn't feel like Python. My guess is a more-Pythonic way would involve OOP with game state object and methods on it that do things and manipulate the state via object attributes.

death cob for cutie
Dec 30, 2006

dwarves won't delve no more
too much splatting down on Zot:4
My thought would be to have each of your "pages" be defined in a JSON file, and when your code starts it grabs the first page from the file, prints the text associated with it, and then each option the user selects takes them to a different page. Maybe have a dictionary set aside to track certain choices if you want to get more involved. Defining a whole class/set of classes around it feels overkill unless you want to do a lot of similar games that may vary just a bit, or if your gamebook aspects are going to get more and more in-depth - I was picturing something very CYOA or Lone Wolf, but if you got more and more detailed you may want to amp up the underlying structure a bit.

12 rats tied together
Sep 7, 2006

what youre trying to do will probably work exactly as is in python, which supports higher order functions/is a functional language, which means your functions can be map values etc. the syntax you have them in has the map key as a list of a single integer which is weird though

i think if you can fit your entire game into code neatly you should definitely do that and not read from the filesystem for every page turn

QuarkJets
Sep 8, 2008

FredMSloniker posted:

I'm looking to make a representation of a gamebook in Python, being familiar with Lua. In Lua, I'd define a game state, a table containing the hero's current stats, inventory, and so forth, as well as the page the reader is currently on. I'd then want to have a function that would take a game state and return a sequence of game states that can be reached with a single decision from that game state. To do so, I'd have a handful of helper functions; for example, freechoice(g, possiblepages) would take the gamestate g and return a sequence of game states, each one with the player having turned to a different page in the sequence possiblepages. Then I'd define this:
code:
local pages = {
	[1] = function(g) return freechoice(g, {2, 3}) end,
	[2] = function(g) return randomchoice(g, {4, 5}) end,
	[3] = function(g) return hasitem(g, "Flask of Wine", {8, 9}) end,
	...
}

local nowturnto = function(g) return pages[g.page](g) end
Am I right in thinking I could do something like this in Python with:
code:
pages = {
	1: lambda g: freechoice(g, [2, 3]),
	2: lambda g: randomchoice(g, [4, 5]),
	3: lambda g: hasitem(g, 'Flask of Wine', [8, 9]),
	...

def nowturnto(g):
	return pages[g['page']](g)
If not, how would I do something like this in Python? Should I do something like this in Python? Is there a more Pythonic way?

e: corrected code.

This is a little confusing, you are passing an entry of g to a dictionary to access a function that takes the entirety of g? I have some random tips:

Use partial instead of lambdas, the difference is subtle but I believe that partial is the better fit here

Try not to use magic numbers. Use enums to specify what 1, 2, 3, etc correspond to; enums are better than integers because they make your code more readable, which will be important if you ever need to look at it again

Don't use single letter variable names except for really ephemeral stuff. lambda g: freechoice(g, [2, 3]), is fine, def nowturnto(g): is just barely over the imaginary line where you should give g a descriptive name.

Don't pass lists around as constant arguments; use tuples instead. And assign those arguments to some variable so that they're described in code. What is [2,3]? The answer should be immediately apparent from the variable name that it's assigned to.

If you're passing around some persistent state (g) between a bunch of functions, you may want to consider defining a class. Maybe call it GameState, give it whatever attributes you think a game state deserves. It can possess methods like nowturnto

Speaking of, use descriptive snake case: turn_to_page is better than nowturnto

Wallet
Jun 19, 2006

cinci zoo sniper posted:

It’s not clear what you are doing or why, so chances are no one has anything meaningful to reply.

Basically this: I'm not sure what exactly you're trying to do or why you're trying to do it the way you are.

If g is a gamestate object I don't know why you'd pass that object to a bunch of functions instead of using a method. I also don't know why you'd want to have a dictionary defining the set of pages instead of just having a Page class with your pages defined by (and loaded from) a more coherent format and a method of that class that does what nowturnto is currently doing so that you can write something like
Python code:
return Gamestate.current_page.get_possible_outcomes()
or whatever instead of
Python code:
return pages[g['page']](g)
.


QuarkJets posted:

Try not to use magic numbers. Use enums to specify what 1, 2, 3, etc correspond to; enums are better than integers because they make your code more readable, which will be important if you ever need to look at it again

I was also going to say something about using lambda to assign functions with contextless numerical arguments to numerical keys in a dictionary but I assume those are actually page numbers?

Wallet fucked around with this message at 00:07 on Nov 14, 2021

galenanorth
May 19, 2016

A while back, I was working with web scraping into CSVs in a project involving selling them, trying to be a rival to aggdata.com. I'd been using an ordered dictionary for each record, which was a mistake because I didn't need to access values using keys, but it would have been helpful for stepwise refinement to be able to directly insert new key-value pairs. For a function which handles a certain aspect of the response, the fields which are easiest to scrape together may not be the fields which are adjacent, so insertion is required. I could have used lists and made it clear which element corresponded to which field using comments instead. By the time I realized, I'd have a lot of code to change, but I've studied database management since, and I'm going to change to using a proper database if I go back to that project anyway.

Python code:
record = OrderedDict({'Store Number': result['storeNumber'],
                      'Store Type': result['storeType'],
                      'Latitude': result['latitude'],
                      'Longitude': result['longitude'],
                      'Phone Number': format_phone_number(result['phoneNumber'], 'US')})
Python code:
# fields that'd be a class attribute elsewhere
fields = ['Store Number', 'Store Type', 'Latitude', 'Longitude', 'Phone Number']
# Within a JSON response-interpreting method
record = [result['storeNumber'], #Store Number
          result['storeType'], # Store Type
          result['latitude'], # Latitude
          result['longitude'], # Longitude
          format_phone_number(result['phoneNumber'], 'US')] # Phone Number
I'm working through HackerRank's collections module unit right now. Maybe a namedtuple would've also worked, but I think anything other than a list or dictionary risks overthinking it, which is what I think of a lot of these collections module classes. I wrote some code for working with 2D lists like dictionaries and working with dictionaries like 2D lists regardless. Maybe I should put it on GitHub later https://pastebin.com/1cnEKAiG

galenanorth fucked around with this message at 03:41 on Nov 14, 2021

QuarkJets
Sep 8, 2008

I'm 100% sure that between a dict, a list, or a named-tuple, you would have been best off using a dataclass instead.

Or possibly a pandas dataframe, but that's pretty weighty. It depends on what you actually want to do with the data

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
All right, let me back up a few steps and try again.

I want to write a program that will 'solve' a gamebook that uses random numbers at some point (Lone Wolf or Fighting Fantasy, for instance). At any given point while playing the gamebook, you have a page that you're on and your hero's condition, which I collectively refer to as a game state. As a first step, I want a function that will take a game state and return the possible game states you can reach with your next decision, as well as a flag to let me know whether it's a free choice or depends on randomness. (I'll use this later when scoring paths through the book.)

In order to not have to repeat a lot of code, I want to break the pages/numbered sections/entries/whatever the given game book calls them into several categories. For instance, it might have a page where you get to freely choose between a number of options. (The number might be one.) It might have a page where you check if you have a given item; if you do, you turn to one page, and if not, you turn to another. It might have a page where you roll a die and turn to a corresponding page. More complicated pages can be broken into internal subpages; for instance, page 10 might say 'if you have this ability, turn to page 27; otherwise, choose whether to turn to page 38 or 76'. I can break that into page 10 ('if you have this ability, turn to page 27; if not, turn to page 10a') and page 10a ('freely choose between pages 38 and 76').

I can then create a function for each category of page that, given a game state and various other arguments, returns a list of the game states resulting from that page and gamestate, as well as a flag letting me know if the choice was random or not. In this case, page 10 would check the game state for whether the character has the specified ability and return a list of a single gamestate, with the page changed to the appropriate page; on the other hand, page 10a would return a list of two gamestates, one for each possible choice. The master function would then check what page the input game state is on, call the appropriate function with the game state and page-specific arguments, and return that output.

How would you go about doing that? Is anything about this description unclear?

QuarkJets
Sep 8, 2008

FredMSloniker posted:

All right, let me back up a few steps and try again.

I want to write a program that will 'solve' a gamebook that uses random numbers at some point (Lone Wolf or Fighting Fantasy, for instance). At any given point while playing the gamebook, you have a page that you're on and your hero's condition, which I collectively refer to as a game state. As a first step, I want a function that will take a game state and return the possible game states you can reach with your next decision, as well as a flag to let me know whether it's a free choice or depends on randomness. (I'll use this later when scoring paths through the book.)

In order to not have to repeat a lot of code, I want to break the pages/numbered sections/entries/whatever the given game book calls them into several categories. For instance, it might have a page where you get to freely choose between a number of options. (The number might be one.) It might have a page where you check if you have a given item; if you do, you turn to one page, and if not, you turn to another. It might have a page where you roll a die and turn to a corresponding page. More complicated pages can be broken into internal subpages; for instance, page 10 might say 'if you have this ability, turn to page 27; otherwise, choose whether to turn to page 38 or 76'. I can break that into page 10 ('if you have this ability, turn to page 27; if not, turn to page 10a') and page 10a ('freely choose between pages 38 and 76').

I can then create a function for each category of page that, given a game state and various other arguments, returns a list of the game states resulting from that page and gamestate, as well as a flag letting me know if the choice was random or not. In this case, page 10 would check the game state for whether the character has the specified ability and return a list of a single gamestate, with the page changed to the appropriate page; on the other hand, page 10a would return a list of two gamestates, one for each possible choice. The master function would then check what page the input game state is on, call the appropriate function with the game state and page-specific arguments, and return that output.

How would you go about doing that? Is anything about this description unclear?

What does the solution to a gamebook look like? Is it a list of unique final game states, and that's why you want to iterate over all possible game states in these decision trees?

death cob for cutie
Dec 30, 2006

dwarves won't delve no more
too much splatting down on Zot:4
okay yeah, IMO at least (I am passingly familiar with the Fighting Fantasy concept but it's been forever since I've touched one of those books or thought about it in detail), you'd want to basically compress the transitions from section to section into a JSON file and load that into memory. (this is so you can do multiple books within the same system, if that's something you care about, and probably better than writing a function or creating an instance of a class representing each game page or w/e - although you could if you hated yourself) you then start the program by calling a function that will start at the beginning page of the book and proceed from there; at each decision point, you'd end up doing a recursive call for each potential outcome you have (succeed on a check, fail a check, proceed with an item, succeed or fail a check only if you have a given item, etc.).

when you hit an end state, you can spit out the tree of decisions leading up to that point/the state of your character at the end of play and then you can sort them based on... total items collected? "bad" vs. "good" endings? number of good random outcomes vs. bad ones? like it may be possible to end the book by saving the Faire Elf Mayden on page 129, but you may or may not get there at full health, you may or may not have X in your inventory, you may have defeated the evil wizard or let him escape, etc. - as long as there are no loops (which I don't recall being a concern in these books) you should be good.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.

QuarkJets posted:

What does the solution to a gamebook look like? Is it a list of unique final game states, and that's why you want to iterate over all possible game states in these decision trees?

For something like a Fighting Fantasy book, I'd be looking at score. Specifically, I score any game state on the final page of the book 1, and any other game state that ends the adventure (whether it's an instant death page or the character has taken too much damage or what) 0. Any game state from which you can make a free decision has the highest score of the game states those decisions lead to. Any choice that must be made randomly has the weighted average score of the game states it leads to. I might, at some point, look at other ways to score things - for instance, since you don't 'win' Fabled Lands books, I might score by how much of the content you're able to see before you die - but that'll work for now. The 'solution' to the gamebook, therefore, is being able to say 'okay, if you make this choice, you have a 40% chance of winning; if you make this choice, you have a 50% chance of winning; and if you make this choice, you're doomed'.

As for loops, that's a concern. The most obvious source of them is combat; in the Fighting Fantasy books, it's possible to draw a given combat round. (Lone Wolf, by contrast, has someone losing health each roll, so combat always ends in a finite number of turns.) I already know how to tweak things to avoid that particular loop, though. I can do loop detection for more general cases by saying that you cannot revisit a given game state more than once in a game (not just a given page, but the exact same situation), and if you would have no choice but to do so at some point, that counts as a loss. (Fighting Fantasy sometimes has you turn to a page to get information, then go back to the previous page. 'Has read this information' would therefore need to be a flag in the game state. I might also apply this to avoid 'cheating', i.e. you have to have read some information to use it in the current run.)

...hm. While looking this over for typos, I've realized this means that a given game state would also have to contain all previous game states in order to be properly scored. I'll have to think on how to solve this at some point that isn't 'I'm about to go to bed'.

At any rate, my biggest concern is memory usage. That's actually why I'm looking at Python in the first place. I can use a database to store something like a dictionary where the keys are serialized current game states and the values contain destination game states, decision types, and scores (if calculated), right? That way I don't have to have the entire state space in memory at once. (I still have to be careful not to let the state space get too big - this isn't going to solve chess - but maybe it can solve checkers.) And Python has a built-in database library.

I admit I don't know a lot about databases, though. Is there some sort of dead-simple instruction on making a database where I can set the value of a (string) key, read the value (if any) of a key, and avoid corrupting the whole thing if the program shuts down early (so I can resume calculations at a later date)? And if two entities would compare identical by value (i.e., if I have two game states that I would consider 'the same'), does json.dumps() return identical strings for both?

QuarkJets
Sep 8, 2008

FredMSloniker posted:

At any rate, my biggest concern is memory usage. That's actually why I'm looking at Python in the first place. I can use a database to store something like a dictionary where the keys are serialized current game states and the values contain destination game states, decision types, and scores (if calculated), right? That way I don't have to have the entire state space in memory at once. (I still have to be careful not to let the state space get too big - this isn't going to solve chess - but maybe it can solve checkers.) And Python has a built-in database library.

That's right

quote:

I admit I don't know a lot about databases, though. Is there some sort of dead-simple instruction on making a database where I can set the value of a (string) key, read the value (if any) of a key, and avoid corrupting the whole thing if the program shuts down early (so I can resume calculations at a later date)? And if two entities would compare identical by value (i.e., if I have two game states that I would consider 'the same'), does json.dumps() return identical strings for both?

Python has a zillion good database libraries so it's a matter of choosing something that makes sense. Do you want a for-realsies database or something that just exists in memory or a file? If you just want to store key-value pairs to disk then h5py (hdf5) is a powerful option, and pytables basically squishes together SQL with hdf5 files in a cool way.

galenanorth
May 19, 2016

QuarkJets posted:

I'm 100% sure that between a dict, a list, or a named-tuple, you would have been best off using a dataclass instead.

Or possibly a pandas dataframe, but that's pretty weighty. It depends on what you actually want to do with the data

Thank you. It's been long enough that I'd started on it before 3.7 came out. My initial plan is just to sell the CSVs as scraped directly from the website, without any other operations done to them except for formatting. My main offering over competitors is that the data is all formatted the same way, instead of formatted inconsistently using the same formatting as the website. When I have enough data sets, I'd start to sell combinations, like "all coffee shop locations in a certain area" or "all restaurants in a certain area that are competitors in selling chicken-based products" and set up an API which charges per-request.

I found out that my state has a minimum $42/month sales tax on top of the $50-$100 in Google advertising expenses, even if I make zero sales in a month, so I'm waiting until I have enough savings to go back to the project, though.
Edit: Actually, I found out I only need to register as a business if I make more than $3,000/year on it, and the minimum tax is only if I make more than $10,000/year. I don't know how I missed that.

galenanorth fucked around with this message at 03:50 on Nov 15, 2021

CarForumPoster
Jun 26, 2013

⚡POWER⚡

galenanorth posted:

Thank you. It's been long enough that I'd started on it before 3.7 came out. My initial plan is just to sell the CSVs as scraped directly from the website, without any other operations done to them except for formatting. My main offering over competitors is that the data is all formatted the same way, instead of formatted inconsistently using the same formatting as the website. When I have enough data sets, I'd start to sell combinations, like "all coffee shop locations in a certain area" or "all restaurants in a certain area that are competitors in selling chicken-based products" and set up an API which charges per-request.

I found out that my state has a minimum $42/month sales tax on top of the $50-$100 in Google advertising expenses, even if I make zero sales in a month, so I'm waiting until I have enough savings to go back to the project, though.
Edit: Actually, I found out I only need to register as a business if I make more than $3,000/year on it, and the minimum tax is only if I make more than $10,000/year. I don't know how I missed that.

As a person who buys marketing data boy howdy this sounds awful. Why would you not put it in a DB, in a standard format for the fields of interest, and then be able to query that DB for whatever a customer might want? Maybe they want coffee shops within 10 miles of a zip code? Obviously you can then offer an API or


If the answer is: because I’m bad at DBs and RDS is expensive, I kinda like Googles big query for stuff I only need to query once in a blue moon and don’t want to spend time setting up a proper dB for. Ive gotten a lot Mileage out of big query for basically no cost and no set up time and lots of Python examples.

galenanorth
May 19, 2016

I got caught up in what a few competitors (RedLionData, ScrapeHero, using Google Places API) were doing rather than what made sense to me. Also, I was thinking of being able to use the codebase I'd already written before learning to use databases to try and sell CSVs while I update to the new version, but it'd probably be better to launch later and make a better first impression. I'm thinking of using Django with PostgreSQL. When it's done or I have a first draft, can I link the website here or in some other thread and let you look over it?

CarForumPoster
Jun 26, 2013

⚡POWER⚡
Yea though I can tell you with big query you get to sidestep the “knows about DBs” part pretty well. It’s worth checking out you can literally make a table from a CSV upload and it has a great query editor and Python examples. Note: I wouldn’t use it as a Django backend.

If you’re thinking of using Django I also highly recommend checking out Zappa. It makes deploying serverless Django a breeze. (Which will also make hosting your site nearly free) It’s honest to god easier to set up than AWS elastic beanstalk/EC2. Maybe not easier than Heroku though.

Edit: also like Zappa because it makes setting up your SSL cert and DNS config super easy if you’re using AWS for those as well (Route 53/ACM is what AWS calls those bits)

EDIT2: Not sure how many opinions youre looking for but I actually have a site that is exactly this use case except I provide other data I scrape. I use Django + Django Rest Framework because it makes auth super easy and secure, Django CMS lets me have marketing pages editable by laypeople, and many other great things about Django. That said, there are definitely much easier, all python ways to do what you want if you're not familiar with Djangos rather opinionated way of doing things so if you'd like more thoughts there LMK.

CarForumPoster fucked around with this message at 14:52 on Nov 16, 2021

Peanut Butler
Jul 25, 2003



hi python pals, humbly asking for a lil help with something p elementary here-

abt fifteen years ago I became Very Depressed and dropped my interests in coding/IT/electronics. I've been in recovery for a couple years now! Prolonged depression left me with some cognitive decline, which I'm finding is reversible, but I have to rebuild a lot of foundational skill rather than jumping back in where I left off. I'm havin fun with it, looking forward to being able to take on lil contracts to help with rent/medicals/food/a better computer than my current pentium g620 box

been coding since BASIC lessons in kindergarten and I'm pushing 40 now, tho I have never coded for money or worked with others (aside from modifying stuff from git/stackoverflow), which brings me to my point:

I want to get a sense for if my coding is idiosyncratic in a way that is not useful- if I'm building algorithms based on old pre-Y2K habits, or, new habits fostered in isolation, that may be less than optimal. I guess I'm asking for a code review? idk industry terms rly, lol, as I said- just been a script jockey for my own hobby purposes, trying to get better at seeking help from other ppl online for things like this.

ok so thx for indulging me in that preface, this year has been a Big Deal for me in terms of recovery, so- it's a bit personally important. On to the code!

I am building a command line interface to aid in running a TTRPG session/campaign. I'm not messing much with classes, structs, or mutators yet- I remember vaguely how they work, but after so much time away I'm finding I have to conceptualize these things all over again (sweigart's work is v useful here, dude knows how to teach a neurodivergent autodidact)- So I have ideas for where I want it to end up, but for now, I'm just cobbling together basic dice functionality- this will take a string in XdY(+/-)N format (or dY, Xd, d+N, etc) and kick out two results: a sum of all dice, and, a list of individual dice rolls. It's also a bit janky because I haven't really done extensive error handling yet, but I've been successfully using it at the game table as a basic dice roller

gmfunctions.py (sans comments):
Python code:
import re
import random

def parsedice(dicestring):	
	values = re.split('[d+-]', dicestring)
	if values[0] == '': 
		values[0] = 1
	else:
		values[0] = int(values[0])
	
	if len(values) <= 1: 
		values.append(6)
	elif values[1] == '':
		values[1] = 6
	else:
		values[1] = int(values[1])
	
	if len(values) <= 2:
		values.append(0)
	elif values[2] == '':
		values[2] = 0
	else:
		if '-' in dicestring:
			values[2] = int(values[2]) * -1
		else:
			values[2] = int(values[2])
	
	return values
	
def rolldice(dicevalues):
	result = 0
	eachdie = []
	diceleft = dicevalues[0]
	while diceleft:
		thisdie = (random.randint(1, dicevalues[1]))
		result += thisdie
		eachdie.append(thisdie)
		diceleft -= 1
	result += dicevalues[2]
	return [result, eachdie]
gmcli.py:
Python code:
import sys
from gmfunctions import parsedice
from gmfunctions import rolldice

while True:
	print('[GMCLI] Main >> ', end='')
	response = input()
	command = response.split(' ')
	
	if command[0] == '':
		print('', end='')
	
	if command[0] == 'roll' or 'r':
		dicestring = command[1]
		diceresults = rolldice(parsedice(dicestring))
		for n in diceresults[1]:
			print(str(n) + " ", end='')
		print("= ", end='')
		print(str(diceresults[0]), end='\n')
	
	elif command[0] == 'exit' or 'quit':
		sys.exit()
		
	else:
		print(response + ': invalid command')
gmfunctions.py with comments: image link
gmcli.py with comments: image link

I get a sense that this is kind of a perly way to do it, but idk if there's anything wrong with that rly

sry if this is too big a post or something, thanks to u for lookin at it!!

Peanut Butler fucked around with this message at 01:17 on Nov 25, 2021

QuarkJets
Sep 8, 2008

Peanut Butler posted:

gmfunctions.py with comments: image link
gmcli.py with comments: image link

I get a sense that this is kind of a perly way to do it, but idk if there's anything wrong with that rly

sry if this is too big a post or something, thanks to u for lookin at it!!

Find yourself a copy of Clean Code. It's Java, not Python, but many of the lessons are still applicable: effective variable names, comments that aren't superfluous or confusing, function/method names that aren't simply lies, etc. I'm not saying any of these things apply to you specifically, I mean in general it's a good book and may help to get you thinking about how you write code and how you can write better code in the future

For style, you can read through PEP8 for specific points, but there are also tools that can help you on your way. PyCharm is a really great IDE and will show you squiggles wherever it senses that you're doing something weirdly. Open your code up in PyCharm and see what it says. I also like flake8, you can run it as a command line utility and it'll dump out a list of everything that you're doing wrong (or that maybe is a little weird).

Let's look at some of your code:

Python code:
import re
import random

def parsedice(dicestring):	
	values = re.split('[d+-]', dicestring)
	if values[0] == '': 
		values[0] = 1
	else:
		values[0] = int(values[0])
	
	if len(values) <= 1: 
		values.append(6)
	elif values[1] == '':
		values[1] = 6
	else:
		values[1] = int(values[1])
	
	if len(values) <= 2:
		values.append(0)
	elif values[2] == '':
		values[2] = 0
	else:
		if '-' in dicestring:
			values[2] = int(values[2]) * -1
		else:
			values[2] = int(values[2])
	
	return values
Instead of "parsedice(dicestring)" I'd call this function "parse_dice(dice_string)"; we're not German (or maybe you are idk), no need to smash words together.

I think it's better to compare string lengths than to compare to an empty string, so if len(values[0]) == 0:

Get in the habit of writing good docstrings. I did look at the version with comments.

Python has neat assignment tricks like dictionary assignment and conditional operators. I'll try to use some so you can check them out.

Unless you really need a list, I think it's preferrable to return tuples. Or better yet, to yield values in a generator, but that wouldn't make sense here.

This code is a little confusing to read without the comments. It would be a lot cleaner and easier to read if you extracted the parameters into named parameters, updated the parameters as-needed, and then returned them as a tuple. Here's how I'd write it:

Python code:
import random
import re

def parse_dice(dice_string):
        quantity_default = 1
        sides_default = 6
        modifier_default = 0

	values = re.split('[d+-]', dicestring)
        
        def parse_value(i, default):
            if len(values) > i:
                if len(values[i]) > 0:
                    return int(values[i])
            return default
        # This also could have been written as a lambda
        # I don't like lambdas but for education, here's the function definition in a single line:
        # parse_value = lambda i, default: int(values[i]) if len(values) > i and len(values[i]) > 0 else default

        quantity = parse_values(0, quantity_default)
        sides = parse_value(1, sides_default)
        modifier = parse_value(2, modifier_default)
        
        if '-' in dice_string:
            modifier *= -1

	return quantity, sides, modifier
I find this easier to read. Parameters are named instead of contained in a list; that's permissible if you explicitly define your indices and assign them to variable names, but for 3 parameters it's better to just extract them fully. Defaults are assigned in a common place. Most of the logic was repeating so it's encapsulated in a small, easy to test function. The function returns a fixed-length tuple of 3 values, anyone can come along and just check the return statement to see that (whereas they'd, or more likely *you'd*, have to look at the logic defining `values` to determine its expected length).

Let's look at the next function:

Python code:
def rolldice(dicevalues):
	result = 0
	eachdie = []
	diceleft = dicevalues[0]
	while diceleft:
		thisdie = (random.randint(1, dicevalues[1]))
		result += thisdie
		eachdie.append(thisdie)
		diceleft -= 1
	result += dicevalues[2]
	return [result, eachdie]
This seems fine, I'd recommend using some kind of iterator rather than a for loop; it's just more compact and easier to read that way. You should read up on list comprehensions and generator expressions, they are incredibly useful. A cut:

Python code:
def roll_dice(dice_values):
        lower_bound = 1
        num_dice, upper_bound, modifier = dice_values
        # Excuse the underscore, it's common parlance for "I don't actually need this iterator's value"
        each_die = tuple(modifier + random.randint(lower_bound, upper_bound) for _ in range(num_dice))
        
        return each_die  
# Note that I don't calculate or return the sum.
# It is better for a function to do a single thing well.
# if you need all of the die results, then this function should just return those.
# You can calculate the sum with the sum() function
That's really just 4 lines of code, and 2 of them are just making the code more readable by using variable names.

Last code block I'm just going to start working on it. Group your imports. Use len(var) == 0 rather than comparing to empty strings. Use 'in' for multiple stringle comparisons. Use f-strings. You can pass objects directly to print() without converting them to strings first, e.g. print(str(some_integer)) is the same as print(some_integer)

Python code:
import sys
from gmfunctions import parse_dice, roll_dice

while True:
	print('[GMCLI] Main >> ', end='')
	response = input()
	command = response.split(' ')
	
	if len(command[0]) == 0:
		print('', end='')
	elif len(command) > 1 and command[0] in ('roll', 'r'):
		dice_string = command[1]
		dice_results = roll_dice(parse_dice(dice_string))
                print(f'{dice_results} = {sum(dice_results)}')
	elif command[0] in ('exit', 'quit', 'e', 'q'):
		break
	else:
		print(f'Invalid command: {response}')
I didn't test any of this code, there may be bugs or syntax issues.

Also, numpy can generate arrays of random integers in a single line, you could forego having a roll_dice function and just write this:
Python code:
import numpy as np
...
quantity, sides, modifier = parse_dice(dice_string)
dice_results = np.random.randint(1, sides, quantity) + modifier
# numpy code is often faster to write and faster to execute than Python for loops
result = dice_results.sum()
Probably want to catch a case where some joker passes in 0 for the sides argument, by using max(sides, 2) to ensure you're always at least flipping coins

QuarkJets fucked around with this message at 09:22 on Nov 25, 2021

Loezi
Dec 18, 2012

Never buy the cheap stuff

QuarkJets posted:

Also, numpy can generate arrays of random integers in a single line, you could forego having a roll_dice function and just write this:
Python code:
import numpy as np
...
quantity, sides, modifier = parse_dice(dice_string)
dice_results = np.random.randint(1, sides, quantity) + modifier
# numpy code is often faster to write and faster to execute than Python for loops
result = dice_results.sum()

Just use random.choices() and sum(), there's no need to start pulling in massive dependencies for stuff like this.

Here's my stab at the dice thrower:
Python code:
import random
import re
import sys
from typing import List, Tuple


def parse(die: str) -> Tuple[int, int, int]:
    die = die.replace(" ", "")  # Ignore extra whitespace
    dice, sides, modifier = re.fullmatch(r"(\d+)d(\d+)([+-]\d+)?", die).groups()
    if dice == "" or sides == "" or int(dice) < 1 or int(sides) < 1:
        raise ValueError("Invalid die string")
    if modifier == "":  # Treat missing modifier as +0
        modifier = 0
    return int(dice), int(sides), int(modifier)


def roll(n: int, sides: int, modifier: int) -> Tuple[int, List[int]]:
    throws = random.choices(range(1, sides + 1), k=n)
    total = sum(throws) + modifier
    return total, throws


def print_result(throws: List[int], modifier: int, total: int) -> None:
    roll_str = "+".join(str(t) for t in throws)
    modifier_str = f"{modifier:+}" if modifier != 0 else ""
    print(f"({roll_str}){modifier_str} = {total}")


def run() -> None:
    while True:
        command, _, argument = input("> ").lower().partition(" ")
        if command in ["exit", "quit"]:
            sys.exit()
        if command in ["r", "roll"]:
            try:
                dice, sides, modifier = parse(argument)
            except Exception:
                print("Invalid input")
                continue
            total, throws = roll(dice, sides, modifier)
            print_result(throws, modifier, total)


if __name__ == "__main__":
    run()

I kept typing in stuff like "r 2d2 + 2" while testing (i.e. extra whitespace) so I just made the thing ignore it for my own sanity.

death cob for cutie
Dec 30, 2006

dwarves won't delve no more
too much splatting down on Zot:4

QuarkJets posted:

Python code:
        each_die = tuple(modifier + random.randint(lower_bound, upper_bound) for _ in range(num_dice))

Is using an underscore in your for loop there common? When I'm teaching for loops in Python students seem to get very confused if we declare a variable there but never actually use it inside the loop - wondering if I could show it using an underscore and say "yeah, this is just a reminder that we're not actually using the number from the range".

Loezi
Dec 18, 2012

Never buy the cheap stuff

Epsilon Plus posted:

Is using an underscore in your for loop there common? When I'm teaching for loops in Python students seem to get very confused if we declare a variable there but never actually use it inside the loop - wondering if I could show it using an underscore and say "yeah, this is just a reminder that we're not actually using the number from the range".

Yes, underscore is the idiomatic way of representing "variable I don't care about and never intend to use". Some common uses:

Python code:
def fart_five_times():
     for _ in range(5):
          print("fart")


five_farts= ['fart' for _ in range(5)]


def method_that_returns_a_tuple():
     return "important", "dont_care", "important"
a, _, b = method_that_returns_a_tuple()
E: A very slightly less contrived example using partition():
Python code:
>>> user_input = "rm fart.txt butts.csv porn.xsl"
>>> user_input.partition(" ")  # note middle value is the separator, which we don't care about
('rm', ' ', 'fart.txt butts.csv porn.xsl')
>>> cmd, _, args = user_input.partition(" ")
>>> cmd
'rm'
>>> args
'fart.txt butts.csv porn.xsl'
>>>

Loezi fucked around with this message at 22:31 on Nov 25, 2021

cinci zoo sniper
Mar 15, 2013




Epsilon Plus posted:

Is using an underscore in your for loop there common? When I'm teaching for loops in Python students seem to get very confused if we declare a variable there but never actually use it inside the loop - wondering if I could show it using an underscore and say "yeah, this is just a reminder that we're not actually using the number from the range".

Yes, it’s an ordinary convention for marking throwaway variables. Please note, however, that _ in the interpreter takes value of the most recently evaluated expression.

QuarkJets
Sep 8, 2008

Epsilon Plus posted:

Is using an underscore in your for loop there common? When I'm teaching for loops in Python students seem to get very confused if we declare a variable there but never actually use it inside the loop - wondering if I could show it using an underscore and say "yeah, this is just a reminder that we're not actually using the number from the range".

Yeah linters even acknowledge this use and will otherwise complain about the unused variable

Wallet
Jun 19, 2006

cinci zoo sniper posted:

Yes, it’s an ordinary convention for marking throwaway variables. Please note, however, that _ in the interpreter takes value of the most recently evaluated expression.

Also convention to use an underscore as a prefix for functions/etc that aren't intended to be imported/used elsewhere. Not sure which convention came first but I think of them as sort of thematically related. Just like the _ on its own there's no special behavior associated with it (except in the case of double underscore prefixes for class attributes which is a whole thang).

cinci zoo sniper
Mar 15, 2013




Wallet posted:

Also convention to use an underscore as a prefix for functions/etc that aren't intended to be imported/used elsewhere. Not sure which convention came first but I think of them as sort of thematically related. Just like the _ on its own there's no special behavior associated with it (except in the case of double underscore prefixes for class attributes which is a whole thang).

There is also a pattern for callbacks, so (sorry for phone formatting)

def print_three():
print(3)

but

def confirm_foo_execution(_):
return True

Wallet
Jun 19, 2006

Loezi posted:

Python code:
def parse(die: str) -> Tuple[int, int, int]......

A fully invalid string is going to return AttributeError instead of ValueError and the blanket try/except in run() makes me squirm a little (and Pycharm for example will squiggle you for this) particularly since it's both catching and masking the actual exception by not at least reporting it, e.g.:

Python code:
            except Exception as e:
                print(f"Invalid input \n {e}")
                continue
Also a missing modifier is still going to throw an exception anyway, no? I think it might have been a thing in python 2.x but "" != None. This should catch it in either case though:
Python code:
def parse(die: str) -> Tuple[int, int, int]:
    die = die.replace(" ", "")  # Ignore extra whitespace
    dice, sides, modifier = re.fullmatch(r"(\d+)d(\d+)([+-]\d+)?", die).groups()
    if dice == "" or sides == "" or int(dice) < 1 or int(sides) < 1:
        raise ValueError("Invalid die string")
    return int(dice), int(sides), int(modifier or 0)

Wallet fucked around with this message at 23:10 on Nov 25, 2021

Peanut Butler
Jul 25, 2003



hey thx for the help! gonna delve into this and prolly have some questions when I get back from thxgiving vacation

Loezi
Dec 18, 2012

Never buy the cheap stuff

Wallet posted:

Also a missing modifier is still going to throw an exception anyway, no? I think it might have been a thing in python 2.x but "" != None. This should catch it in either case though:
Python code:
def parse(die: str) -> Tuple[int, int, int]:
    die = die.replace(" ", "")  # Ignore extra whitespace
    dice, sides, modifier = re.fullmatch(r"(\d+)d(\d+)([+-]\d+)?", die).groups()
    if dice == "" or sides == "" or int(dice) < 1 or int(sides) < 1:
        raise ValueError("Invalid die string")
    return int(dice), int(sides), int(modifier or 0)

Whoops, that what I get for making a last second swap from findall to fullmatch. Nicely spotted.

cinci zoo sniper
Mar 15, 2013




PyCharm 2021.3 is revamping notebooks and remote development again. Alexa, play Deja Vu by Initial D.

Macichne Leainig
Jul 26, 2012

by VG
Is working with notebooks in Pycharm currently bad? I don't do notebooks too much and when I do I honestly just use JupyterLab which seems good.

cinci zoo sniper
Mar 15, 2013




Protocol7 posted:

Is working with notebooks in Pycharm currently bad? I don't do notebooks too much and when I do I honestly just use JupyterLab which seems good.

Working with literal notebooks does fail to utilize the available functionality properly, in my opinion. It's a functional experience, has been for years, but I still feel nowhere near "native" development experience. Also, some of the more involved notebook setups won't work at all in PyCharm, but I only use them when I'm pressed to do so.

Working in scientific mode, via # %% comment notation, feels great, however. The introduction of the notation was like their third or fourth attempt at making the notebook-style coding experience suck less, which is what my post was getting at - they've continuously failed at making a dent in the USP of VSCode or Jupyter. Fleet is now a dedicated attempt to take a crack at VSCode's remote development faculties, whereas this is the other half then, and will get ported to Fleet if it works.

Adbot
ADBOT LOVES YOU

hbag
Feb 13, 2021

man
im really not sure if this should go in this thread or the HTML thread but uhh

i'm making a REST API in flask, ive got some of this poo poo set up:
Python code:
@app.route('/flags/user/<string:search_user>', methods=['GET','POST'])
def userflags(search_user):
    return userflags_actual(search_user)
works great, but ive got a search form on one of the pages, and i want it to navigate to the appropriate page when they submit, obviously
so for example if someone were to search 'hbag', it'd take them to '/flags/users/hbag'

but since it's a HTML form, i have no idea how to get around that, since adding an 'onsubmit' function seems to just run that function and THEN submit the form regularly anyway

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply