chem.py

Created by laigna

Created on March 14, 2026

7.62 KB

Chemistry solver with molar mass calculator, moles/mass conversions, gas volume at STP, solution dilution (C1V1=C2V2), pH calculator, mass percentage, and element table.


# Chem Solver - NumWorks
# Created by Alvar Laigna - https://alvarlaigna.com
# Arrows:select OK:choose Back:back
from kandinsky import fill_rect as F,draw_string as D
from ion import keydown as K
from time import sleep as Z
from math import log
SW,SH=320,222
BK=(0,)*3;WH=(255,)*3
BL=(50,110,230);TL=(0,190,190)
YL=(220,200,60);DG=(120,)*3

okv=1 if K(4)or K(52) else 0

def okd():
 return K(4)or K(52)

def okp():
 global okv
 d=okd()
 if d and not okv:
  okv=1
  return True
 if not d:okv=0
 return False

def wup():
 while okd():Z(0.02)
 Z(0.12)

EL={"H":1.008,"He":4.003,"Li":6.941,"Be":9.012,
"B":10.81,"C":12.011,"N":14.007,"O":15.999,
"F":18.998,"Ne":20.18,"Na":22.99,"Mg":24.305,
"Al":26.982,"Si":28.086,"P":30.974,"S":32.065,
"Cl":35.453,"Ar":39.948,"K":39.098,"Ca":40.078,
"Ti":47.867,"Cr":51.996,"Mn":54.938,"Fe":55.845,
"Co":58.933,"Ni":58.693,"Cu":63.546,"Zn":65.38,
"Br":79.904,"Ag":107.87,"Sn":118.71,"I":126.9,
"Ba":137.33,"Au":196.97,"Hg":200.59,"Pb":207.2}

def menu(title,items,sub=False):
 sel=0;n=len(items)
 while True:
  F(0,0,SW,SH,BK)
  D(title,SW//2-len(title)*5,6,TL,BK)
  F(0,24,SW,2,(40,)*3)
  for i in range(n):
   y=34+i*22
   if i==sel:
    F(8,y,SW-16,20,BL)
    D("> "+items[i],14,y+2,WH,BL)
   else:
    D("  "+items[i],14,y+2,DG,BK)
  if sub:D("Back=return",100,SH-16,(60,)*3,BK)
  else:D("Back=quit",115,SH-16,(60,)*3,BK)
  while True:
   if K(1):sel=(sel-1)%n;Z(0.15);break
   if K(2):sel=(sel+1)%n;Z(0.15);break
   if okp():wup();return sel
   if K(17):return-1
   Z(0.04)

def inp(prompt):
 try:return float(input(prompt))
 except:return None

def inps(prompt):
 return input(prompt).strip()

def show(lines):
 F(0,0,SW,SH,BK)
 D("RESULT",130,6,TL,BK)
 F(0,24,SW,2,(40,)*3)
 for i,ln in enumerate(lines):
  c=YL if i==len(lines)-1 else WH
  D(str(ln),14,34+i*20,c,BK)
 D("OK=back",130,SH-16,DG,BK)
 while True:
  if okp()or K(17):wup();return
  Z(0.04)

def parse(fm):
 el={};i=0
 while i<len(fm):
  if fm[i].isupper():
   s=fm[i];i+=1
   while i<len(fm) and fm[i].islower():
    s+=fm[i];i+=1
   n=""
   while i<len(fm) and fm[i].isdigit():
    n+=fm[i];i+=1
   n=int(n) if n else 1
   el[s]=el.get(s,0)+n
  else:i+=1
 return el

def molar(fm):
 el=parse(fm);m=0
 for s,n in el.items():
  if s not in EL:return None,s
  m+=EL[s]*n
 return m,None

def f_molar():
 fm=inps("Formula (eg H2O): ")
 if fm:
  m,err=molar(fm)
  if err:show(["Unknown element: "+err])
  elif m:
   el=parse(fm)
   r=["Formula: "+fm]
   for s,n in el.items():
    r+=[s+": "+str(n)+"x"+str(EL[s])+"="+str(round(EL[s]*n,3))]
   r+=["M = "+str(round(m,4))+" g/mol"]
   show(r)

def f_moles():
 c=menu("Moles / Mass",
  ["n = m / M  (find moles)",
   "m = n x M  (find mass)",
   "M = m / n  (find molar mass)"],True)
 if c==0:
  m=inp("Mass m (g): ");mm=inp("Molar mass M (g/mol): ")
  if m is not None and mm and mm!=0:
   show(["n = m / M",
    "m="+str(m)+"g  M="+str(mm)+"g/mol",
    "n = "+str(round(m/mm,6))+" mol"])
 elif c==1:
  n=inp("Moles n (mol): ");mm=inp("Molar mass M (g/mol): ")
  if n is not None and mm is not None:
   show(["m = n x M",
    "n="+str(n)+"mol  M="+str(mm)+"g/mol",
    "m = "+str(round(n*mm,6))+" g"])
 elif c==2:
  m=inp("Mass m (g): ");n=inp("Moles n (mol): ")
  if m is not None and n and n!=0:
   show(["M = m / n",
    "m="+str(m)+"g  n="+str(n)+"mol",
    "M = "+str(round(m/n,4))+" g/mol"])

def f_gas():
 c=menu("Gas at STP (22.4 L/mol)",
  ["V = n x Vm  (find volume)",
   "n = V / Vm  (find moles)",
   "From mass (enter M)"],True)
 if c==0:
  n=inp("Moles n (mol): ")
  if n is not None:
   show(["V = n x Vm (STP)",
    "n="+str(n)+" mol  Vm=22.4 L/mol",
    "V = "+str(round(n*22.4,4))+" L"])
 elif c==1:
  v=inp("Volume V (L): ")
  if v is not None:
   show(["n = V / Vm (STP)",
    "V="+str(v)+" L  Vm=22.4 L/mol",
    "n = "+str(round(v/22.4,6))+" mol"])
 elif c==2:
  m=inp("Mass m (g): ");mm=inp("Molar mass M (g/mol): ")
  if m is not None and mm and mm!=0:
   n=m/mm;v=n*22.4
   show(["Gas from mass (STP)",
    "m="+str(m)+"g  M="+str(mm)+"g/mol",
    "n = "+str(round(n,6))+" mol",
    "V = "+str(round(v,4))+" L"])

def f_dilute():
 c=menu("Dilution C1V1=C2V2",
  ["Find C2 (final conc)",
   "Find V2 (final volume)",
   "Find C1 (initial conc)",
   "Find V1 (initial volume)"],True)
 if c==0:
  c1=inp("C1 (mol/L): ");v1=inp("V1 (L): ")
  v2=inp("V2 (L): ")
  if c1 is not None and v1 is not None and v2 and v2!=0:
   show(["C2 = C1*V1/V2",
    "C1="+str(c1)+" V1="+str(v1)+" V2="+str(v2),
    "C2 = "+str(round(c1*v1/v2,6))+" mol/L"])
 elif c==1:
  c1=inp("C1 (mol/L): ");v1=inp("V1 (L): ")
  c2=inp("C2 (mol/L): ")
  if c1 is not None and v1 is not None and c2 and c2!=0:
   show(["V2 = C1*V1/C2",
    "C1="+str(c1)+" V1="+str(v1)+" C2="+str(c2),
    "V2 = "+str(round(c1*v1/c2,6))+" L"])
 elif c==2:
  v1=inp("V1 (L): ");c2=inp("C2 (mol/L): ")
  v2=inp("V2 (L): ")
  if v1 and v1!=0 and c2 is not None and v2 is not None:
   show(["C1 = C2*V2/V1",
    "V1="+str(v1)+" C2="+str(c2)+" V2="+str(v2),
    "C1 = "+str(round(c2*v2/v1,6))+" mol/L"])
 elif c==3:
  c1=inp("C1 (mol/L): ");c2=inp("C2 (mol/L): ")
  v2=inp("V2 (L): ")
  if c1 and c1!=0 and c2 is not None and v2 is not None:
   show(["V1 = C2*V2/C1",
    "C1="+str(c1)+" C2="+str(c2)+" V2="+str(v2),
    "V1 = "+str(round(c2*v2/c1,6))+" L"])

def f_ph():
 c=menu("pH Calculator",
  ["pH from [H+]",
   "[H+] from pH",
   "pOH from pH",
   "pH from pOH"],True)
 if c==0:
  h=inp("[H+] (mol/L): ")
  if h and h>0:
   ph=-log(h)/log(10)
   show(["pH = -log10[H+]",
    "[H+] = "+str(h)+" mol/L",
    "pH = "+str(round(ph,4)),
    "pOH = "+str(round(14-ph,4))])
 elif c==1:
  ph=inp("pH: ")
  if ph is not None:
   h=10**(-ph)
   show(["[H+] = 10^(-pH)",
    "pH = "+str(ph),
    "[H+] = "+str(h)+" mol/L",
    "pOH = "+str(round(14-ph,4))])
 elif c==2:
  ph=inp("pH: ")
  if ph is not None:
   show(["pOH = 14 - pH",
    "pH = "+str(ph),
    "pOH = "+str(round(14-ph,4))])
 elif c==3:
  poh=inp("pOH: ")
  if poh is not None:
   show(["pH = 14 - pOH",
    "pOH = "+str(poh),
    "pH = "+str(round(14-poh,4))])

def f_masspct():
 c=menu("Mass Percentage",
  ["w% (find percentage)",
   "Find solute mass",
   "Find solution mass"],True)
 if c==0:
  ms=inp("Solute mass (g): ");mt=inp("Solution mass (g): ")
  if ms is not None and mt and mt!=0:
   show(["w% = m_solute/m_solution x100",
    "m_solute="+str(ms)+"g",
    "m_solution="+str(mt)+"g",
    "w = "+str(round(ms/mt*100,4))+" %"])
 elif c==1:
  w=inp("Mass % (w%): ");mt=inp("Solution mass (g): ")
  if w is not None and mt is not None:
   show(["m_solute = w%/100 x m_solution",
    "w="+str(w)+"%  m_sol="+str(mt)+"g",
    "m_solute = "+str(round(w/100*mt,4))+" g"])
 elif c==2:
  ms=inp("Solute mass (g): ");w=inp("Mass % (w%): ")
  if ms is not None and w and w!=0:
   show(["m_solution = m_solute/(w%/100)",
    "m_solute="+str(ms)+"g  w="+str(w)+"%",
    "m_solution = "+str(round(ms/(w/100),4))+" g"])

def f_elem():
 keys=sorted(EL.keys())
 sel=0;n=len(keys);vis=7
 while True:
  F(0,0,SW,SH,BK)
  t="Elements ("+str(n)+")"
  D(t,SW//2-len(t)*5,6,TL,BK)
  F(0,24,SW,2,(40,)*3)
  top=max(0,min(sel-vis//2,n-vis))
  for i in range(vis):
   idx=top+i
   if idx>=n:break
   y=34+i*22
   s=keys[idx]
   txt=s+" - "+str(EL[s])+" g/mol"
   if idx==sel:
    F(8,y,SW-16,20,BL)
    D("> "+txt,14,y+2,WH,BL)
   else:
    D("  "+txt,14,y+2,DG,BK)
  D("Back=return",100,SH-16,(60,)*3,BK)
  while True:
   if K(1):sel=(sel-1)%n;Z(0.15);break
   if K(2):sel=(sel+1)%n;Z(0.15);break
   if K(17):return
   Z(0.04)

CATS=["Molar Mass Calculator",
 "Moles / Mass / M",
 "Gas Volume (STP)",
 "Dilution (C1V1=C2V2)",
 "pH Calculator",
 "Mass Percentage",
 "Element Table"]
FNS=[f_molar,f_moles,f_gas,f_dilute,
 f_ph,f_masspct,f_elem]
def run():
 while True:
  c=menu("Chem Solver",CATS)
  if c<0:return
  FNS[c]()
run()

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.