monopoly4.py

Created by elnix91

Created on September 28, 2025

16.9 KB

debug version monopoly, feel free to finish it; the last version is available here


from kandinsky import fill_rect as rect,draw_string as txt,set_pixel as pix
from random import randint,choice
from ion import keydown as k
import time

SKY=(0,0,0)
TXT=(255,)*3
IMP=(0,0,255)
SEL=(255,255,0)
PC=[(200,0,200),(0,255,255),(255,0,255),(255,200,0),(255,0,0),(255,255,0),(0,150,0),(0,0,255)]

savefile="monopoly.sav"
# Types of cases:
# 0:proprety (22+4+2)
# 1:gain/pay money (3)
# 4:luck (3)
# 5:box (3)
# 6:jail (1)
# 7:go to jail (1)
# 8:free parc (1)
CASES=[
["Start",1,20],
["Mediterranean Ave",0,0,0,6,0.2,1,3,9,16,25],
["Community Chest",5],
["Baltic Avenue",0,1,0,6,0.4,2,6,18,32,45],
["Income Tax",1,-20],
["Reading RR",0,22,1,20,2.5],
["Oriental Avenue",0,2,1,10,0.6,3,9,27,40,55],
["Chance",4],
["Vermont Avenue",0,3,1,10,0.6,3,9,27,40,55],
["Connecticut Ave",0,4,1,12,0.8,4,10,30,45,60],
["Jail",6],
["St Charles Place",0,5,2,14,1,5,15,45,62.5,75],
["Electric Company",0,26,5,15],
["States Avenue",0,6,2,14,1,5,15,45,62.5,75],
["Virginia Avenue",0,7,2,16,1.2,6,18,50,70,90],
["Pennsylvania RR",0,23,1,20,2.5],
["St. James Place",0,8,3,18,1.4,7,20,55,75,95],
["Community Chest",5],
["Tennessee Avenue",0,9,3,18,1.4,7,20,55,75,95],
["New York Avenue",0,10,3,20,1.6,8,22,60,80,100],
["Free Parking",8],
["Kentucky Avenue",0,11,4,22,1.8,9,25,70,87.5,105],
["Chance",4],
["Indiana Avenue",0,12,4,22,1.8,9,25,70,87.5,105],
["Illinois Avenue",0,13,4,24,2,10,30,75,92.5,110],
["B&O RR",0,24,1,20,2.5],
["Atlantic Avenue",0,14,5,26,2.2,11,33,80,97.5,115],
["Ventnor Avenue",0,15,5,26,2.2,11,33,80,97.5,115],
["Water Works",0,27,5,15],
["Marvin Gardens",0,16,5,28,2.4,12,36,85,102.5,120],
["Go to Jail",7],
["Pacific Avenue",0,17,6,30,2.6,13,39,90,110,127.5],
["North Carolina Ave",0,18,6,30,2.6,13,39,90,110,127.5],
["Community Chest",5],
["Pennsylvania Ave",0,19,6,32,2.8,15,45,100,120,140],
["Short Line",0,25,1,20,2.5],
["Chance",4],
["Park Place",0,20,7,35,3.5,17.5,50,110,130,150],
["Luxury Tax",1,-10],
["Boardwalk",0,21,7,40,5,20,60,140,170,200]
]
CHANCES=[
[["Go to jail!"],5],
[["Free from jail"],0],
# [["Advance to Boardwalk"],2,21],
# [["Advance to Go"],2,0],
# [["Advance to Illinois Ave"],2,19],
# [["Advance to St. Charles"],2,5],
# [["Bank pays you"],1,5],
# [["Get out of jail"],0],
# [["Go back 3 spaces"],4],
# [["Go to Reading RR"],2,22],
# [["Make repairs"],3,[2.5,10]],
# [["Pay poor tax"],1,-15],
# [["Trip to Pennsylvania RR"],2,24],
# [["Building loan matures"],1,15],
# [["Win crossword contest"],1,10]
]
CHESTS=[
[["Go to jail!"],5],
[["Free from jail"],0],
[["Advance to Go"],2,0],
# [["Bank error, collect"],1,20],
# [["Doctor fees, pay"],1,-10],
# [["Stock sale, get"],1,5],
# [["Get out of jail"],0],
# [["Grand Opera Night"],1,5],
# [["Holiday fund matures"],1,10],
# [["Tax refund, collect"],1,2],
# [["Birthday, collect"],6,1],
# [["Life insurance matures"],1,10],
# [["Pay hospital fees"],1,-10],
# [["Pay school fees"],1,-5],
# [["Consultancy fee"],1,25],
# [["You inherit"],1,10]
]

def shuffle(l):
  n=len(l)
  for i in range(len(l)-1,0,-1):
    j=randint(0,i)
    l[i],l[j]=l[j],l[i]
  return l

try:
  import os
  OS=True
except:OS=False

clean=lambda:rect(0,0,320,222,SKY)
txtc=lambda t,y,fc,bc:txt(t,160-len(t)*5,y,fc,bc)
s=lambda:k(0)|k(1)|k(2)|k(3)
v=lambda:k(4)|k(52)


#           toggle  text/number                                        selector      just select
# types : [[0,x], [1,x,maxLenght,minlenght,numbers/letters(Bool)],   [2,x,min,max],     [3]]
def popup(x,y,w,h,xso,yso,c,title,opt,types,result=[],frame=True,padding=2,back=False,retname=False):
  if frame:
    rect(x,y,w,h,c[0])
    rect(x+3,y+3,w-6,h-6,SKY)
    y+=2
  else:rect(x,y,w,h,SKY)
  for i,j in enumerate(title):
    txtc(j,y+2+(18+padding)*i,c[1],SKY)

  ntv=False
  for i,o in enumerate(opt):
    txt(o,xso,yso+(18+padding)*i,c[2],SKY)
    if types[i][0]==0:
      switch(types[i][1],yso+(18+padding)*i,result[i])
      ntv=True
    elif types[i][0]==1:
      rect(types[i][1],yso+(18+padding)*i+17,10*types[i][2],1,IMP)
      ntv=True
    elif types[i][0]==2:
      txt("<   >",types[i][1]-20,yso+(18+padding)*i,IMP,SKY)
  if ntv:
    txt("VALIDATE",xso,yso+(18+padding)*(i+1),(0,255,0),SKY)
    types.append([3])
  r=0
  first=True
  lastAct=-1
  while v():1
  while 1:
    if s()|first:
      lastAct=r
      r=(r-k(1)+k(2))%len(types)
      if types[r][0]==2:
        result[r]=(result[r]-k(0)+k(3))%(types[r][3]-types[r][2]+1)
        txt(str(result[r]+types[r][2]),types[r][1],yso+(18+padding)*r,IMP,SKY)
      rect(xso-20,yso+(18+padding)*lastAct,10,18,SKY)
      txt("",xso-20,yso+(18+padding)*r,SEL,SKY)
      if first:first=False
      while s():1

    elif v():
      if not ntv:
        if retname:return opt[r]
        return r
      if types[r][0]==0:
        result[r]=not result[r]
        switch(types[r][1],yso+(18+padding)*r,result[r])
        while v():1
      elif types[r][0]==1:
        result[r]=kinput(types[r][1],yso+(18+padding)*r,types[r][2],types[r][3],types[r][4])
      
      elif types[r][0]==3:
        if retname:
          return opt[r]
        return tuple(result)

    elif k(17)&back:return -1

def switch(x,y,on):
  rect(x,y,18,18,(200,)*3)
  rect(x+1,y+1,16,16,SKY)
  if on:c=(0,255,0)
  else:c=(100,)*3
  rect(x+3,y+3,12,12,c)

def kinput(x,y,maxLenght=4,minLenght=4,onlynumbers=True,ct=TXT):
  letters={29: 'l', 30: 'm', 31: 'n', 32: 'o', 33: 'p', 34: 'q', 36: 'r', 37: 's', 38: 't', 39: 'u', 40: 'v', 42: 'w', 43: 'x', 44: 'y', 45: 'z', 18: 'a', 19: 'b', 20: 'c', 21: 'd', 22: 'e', 23: 'f', 24: 'g', 25: 'h', 26: 'i', 27: 'j', 28: 'k'}
  numbers={30: '7', 31: '8', 32: '9', 42: '1', 43: '2', 44: '3', 36: '4', 37: '5', 38: '6', 48: '0'}
  Input=""
  rect(x,y,10*maxLenght,18,SKY)
  rect(x,y+17,10*maxLenght,1,IMP)
  rect(x,y+2,1,14,TXT)
  while v():1
  while 1:
    for i in range(17,49):
      if k(i):
        if onlynumbers:
          if(i in numbers.keys())&(len(Input)<maxLenght):
            Input+=numbers[i]
        else:
          if(i in letters.keys())&(len(Input)<maxLenght):
            Input+=letters[i]
        if (i==17)&(len(Input)>0):
          Input=Input[:-1]
          rect(x+len(Input)*10,y,12,18,SKY)
        rect(x,y+17,10*maxLenght,1,IMP)
        rect(x+len(Input)*10+1,y+2,1,14,TXT)
        txt(Input,x,y,ct,SKY)
        while k(i):1
    if v():
      if len(Input)>=minLenght:
        rect(x+len(Input)*10+1,y+2,1,14,SKY)
        return Input
      else:
        txt(Input,x,y,(255,0,0),SKY)
        time.sleep(0.5)
        txt(Input,x,y,ct,SKY)


def draw_case(x,y,id,rot=0):
  t=CASES[id][1]
  rect(x,y,20,20,TXT)
  rect(x+1,y+1,18,18,SKY)
  if t==0:
    if 21<CASES[id][2]<26: #Train stations
      rect(x+2,y+2,16,16,(50,)*3)
      rect(x+4,y+4,12,12,SKY)
      rect(x+6,y+6,8,8,(50,)*3)
    elif CASES[id][2]>25: # Services
      if CASES[id][2]==0:c=(0,0,200)
      else:c=SEL
      txt("S",x+5,y+1,c,SKY)

    else: # Propreties
      xs1,ys1,w1,h1,xs2,ys2,w2,h2=x+1,y+1,18,5,x+1,y+7,18,12
      if rot==1:xs1,w1,h1,ys2,w2,h2=x+14,5,18,y+1,12,18
      elif rot==2:ys1,ys2=y+14,y+1
      elif rot==3:w1,h1,xs2,ys2,w2,h2=5,18,x+7,y+1,12,18
      rect(xs1,ys1,w1,h1,PC[CASES[id][3]])
      for i in range(N):
        if id in A[i]["terrains"]:
          rect(xs2,ys2,w2,h2,PC[i])

      if 0<A[-2][CASES[id][2]]<5:
        xi,yi=0,0
        if w1>h1:xi=4
        else:yi=4
        for i in range(A[-2][CASES[id][2]]):
          rect(xs1+1+xi*i,ys1+1+yi*i,3,3,SKY)
      elif A[-2][CASES[id][2]]==5:
        rect(xs1+1,ys1+1,3,3,TXT)
  elif t==1: # Start/Taxes
    if CASES[id][2]>0:c=(0,200,0)
    else:c=(200,0,0)
    rect(x+2,y+2,16,16,c)
  elif 3<t<6: # Chance/Community Chest
    if t==4:c=(0,0,255)
    else:c=(255,0,0)
    txt("C",x+5,y+1,c,SKY)
  elif t==6: # Jail
    rect(x+1,y+1,18,18,(255,100,0))
    rect(x+1,y+7,12,12,(70,)*3)
  elif t==7:rect(x+3,y+3,14,14,(255,100,0)) # Go to jail
  elif t==8: # Free park
    rect(x+2,y+2,16,16,(0,255,0))
    rect(x+4,y+4,12,12,(0,180,0))
# Draw players
  np=[]
  for i in range(N):
    if A[i]["case"]==id:
      np.append(i)
  if len(np)>0:
    rect(x+5,y+7,10,6,SKY)
    for _ in range(2):
      for __ in range(4):
        if _*4+__ in np:rect(x+6+__*2,y+8+_*2,2,2,PC[_*4+__])

def menu():
  global A
  clean()
  c=0
  if OS:
    if savefile in os.listdir():
      c=popup(0,0,320,222,135,150,[PC[0],PC[3],IMP],["Savefile found!","Do you want to","continue the game?"],["YES","NO"],[[3],[3]])
      if c==0:
        with open(savefile,"r")as f:
          A=eval(f.read())
          if"code"in A[0].keys():Password=True
          else:Password=False
        return len(A)-6,Password,True
  opt=["Number of players:","Password protection:"]
  types=[[2,260,2,8],[0,260]]
  if OS:
    opt.append("Enable saving:")
    types.append([0,260])
  params=popup(0,0,320,222,30,70,[PC[1],PC[3],IMP],["MONOPOLY - Made by Elnix"],opt,types,[0,False,False],padding=22)
  N=params[0]+2
  A=[{"name":"","money":150000,"terrains":[],"hyp":[],"case":0,"free":False} for i in range(N)]
  for i in range(N):
    clean()
    txt("Player "+str(i+1),30,20,PC[i],SKY)
    txt(", enter your name:",110,20,TXT,SKY)
    A[i]["name"]=kinput(110,70,10,False)
    if params[1]:
      txt("Please set a up a pin code:",25,110,TXT,SKY)
      while 1:
        code=kinput(140,140)
        if len(code)<4:txt("Too short!",110,170,(255,0,0),SKY)
        else:
          A[i]["code"]=code
          break
  A.append([33,11])
  A.append(shuffle([i for i in range(18)]))
  A.append(shuffle([i for i in range(18)]))
  A.append([i for i in range(22)])
  A.append([0 for i in range(22)])
  A.append(0)
  return N,params[1],params[2]

def getHousePrice(id):
  if id<10:return 5000
  elif id<20:return 10000
  elif id<30:return 15000
  else:return 20000

def draw_terrain():
  id=0
  for i in range(10):
    draw_case(1+i*20,1,id,0)
    id+=1
  for i in range(10):
    draw_case(201,1+i*20,id,1)
    id+=1
  for i in range(10):
    draw_case(201-i*20,201,id,2)
    id+=1
  for i in range(10):
    draw_case(1,201-i*20,id,3)
    id+=1
  txt("Total in",70,50,IMP,SKY)
  txt("Free park:",60,70,IMP,SKY)
  txt(str(A[-1]),110-len(str(A[-1]))*5,90,IMP,SKY)

def calc_rent(id,All=False,owner=None):
  global DR
  p=[5000,2500,5000,10000,20000]
  if CASES[id][2]<26:
    if All:
      if CASES[id][2]<22:return CASES[id][4:]
        # # p=[CASES[id][4]] #Case price
        # for i in range(7):
        #   p.append(CASES[id][4+i]) # CasePrice/Greenfield/houses/hotel
      return p
    else:
      if CASES[id][2]<22:
        return CASES[id][4+A[-2][CASES[id][2]]]
      else:
        nbts=0
        for i in range(22,26):
          if i in A[owner]["terrains"]:nbts+=1
        return p[nbts+1]
  else:
    if All:return
    nbs=0
    if 28 in A[owner]["terrains"]:nbs+=1
    if 27 in A[owner]["terrains"]:nbs+=1
    if nbs==1:return 400*DR
    return 1000*DR


def draw_rent(x,y,id):
  r=calc_rent(id,True)
  if CASES[id][2]<26:
    for i,j in enumerate(r):
      if i==0:txt("Base Price:",x,y,TXT,SKY)
      if CASES[id][2]<22:
        if i==1:txt("Terrain:",x,y+20,TXT,SKY)
        elif i==6:txt("Hotel:",x,y+120,TXT,SKY)
        elif i>0:txt(str(i-1)+" houses",x,y+20*i,TXT,SKY)
        txt(str(j),x+120,y+20*i,PC[4],SKY)
      else:
        if i>0:txt(str(i)+" stations:",x,y+20*i,TXT,SKY)
        txt(str(j),x+120,y+20*i,PC[4],SKY)
  else:
    txt("1 serv: 400*Dices",x,y,TXT,SKY)
    txt("2 serv: 1000*Dices",x,y+20,TXT,SKY)

def selectP(l):
  clean()
  txtc("Select a propriety:",10,IMP,SKY)
  act=0
  first=True
  while v():1
  while not v():
    if s()|first:
      act=(act-k(0)+k(3))%len(l)
      clean()
      rect(0,40,320,18,SKY)
      txtc("< "+CASES[l[act]][0]+" >",40,TXT,SKY)
      draw_case(20,100,l[act])
      draw_rent(110,75,l[act])
      if first:first=False
      while s():1
    elif k(17):
      return -1
  return l[act]
        
def drawC(P,t):
  clean()
  if t:
    id=-5
    txtc("CHANCE",10,IMP,SKY)
  else:
    id=-4
    txtc("COMMUNITY CHEST",10,IMP,SKY)
  for i,j in enumerate(CHANCES[A[id][0]][0]):
    txtc(j,35+i*20,TXT,SKY)
  c=A[id][0][1]
  if c==0: # Free from jail
    A[P]["free"]=True
  elif c==1: # Pay/gain amount
    if t:amount=CHANCES[A[-5][0]][2]
    else:amount=CHESTS[A[-4][0]][2]
    if amount>0:A[P]["money"]+=amount
    else:pay(P,-1,amount,True)
  elif c==2: # Move to
    pass
  elif c==3: #Pay houses
    nbhouses=0
    nbhotels=0
    for i in A[P]["terrains"]:
      n=A[-2][CASES[i][2]]
      if 0<n<5:nbhouses+=n
      else:nbhotels=+n
    amount=nbhouses*CHANCES[A[-5][0]][2]+nbhotels*CHANCES[A[-5][0]][3]
    rp=pay(P,-1,amount)
  elif c==4: # move backwards
    pass
  elif c==5: # Go jail
    pass
    
  
  if c:A[id].append(A[id][0])
  del [id][0]
  return rp



#-1:cancel,0:fine,1:one lost, 2:game ends
def pay(p,r,amount,middle=False,canCancel=False):
  while A[p]["money"]<amount:
    opt=[]
    houses={}
    for i in A[p]["terrains"]:
        if CASES[i][2]<22:houses[i]=A[-2][CASES[i][2]]
        else: houses[i]=0
    if sum(houses.values())>0:
      opt.append("Sell houses")
    if 0 in houses.values():opt.append("Mortgage")
    if len(opt)==0:
      if lose(p):return 1
      else:return 2
    code=popup(10,10,300,202,40,90,[PC[4],PC[4],IMP],["Warning! Not enough money!","Your money: "+str(A[p]["money"]),"To pay: "+str(amount)],opt,[[3] for i in range(len(opt))],back=canCancel,retname=True)
    if code=="Sell houses": # Sell houses
      htcbs=[i for i,j in houses.items() if j>0]
      s=selectP(htcbs)
      if s==-1:continue
      else:
        A[-2][CASES[s][2]]-=1
        A[p]["money"]+=getHousePrice(s)//2
        continue
        
    elif code=="Mortgage": # Mortgage
      htcbs=[i for i,j in houses.items() if j==0]
      s=selectP(htcbs)
      if s==-1:continue
      else:
        A[p]["terrains"].remove(s)
        A[p]["hyp"].append(s)
        # if  CASES[s][2]<22:
        A[p]["money"]+=CASES[s][4]//2

    else:return -1 # Cancel

  A[p]["money"]-=amount
  if middle:A[-1]+=amount
  if r!=-1:A[r]["money"]+=amount
  return 0

def lose(p):
  global N
  N-=1
  for i in A[p]["hyp"]:
    A[-3].append(i)
  rect(20,20,280,182,PC[p])
  rect(23,23,274,176,SKY)
  if N>1:
    txtc(A[p]["name"]+" lose!",70,PC[p],SKY)
    txtc("BUT THE GAME CONTINUE!",110,TXT,SKY)
  del A[p]
  if N==1:
    txtc("Congrat to "+A[0]["name"],70,PC[p],SKY)
    txtc("For winning this game!",110,TXT,SKY)
    while v():1
    while not v():1
    return False
  else:
    while v():1
    while not v():1
    return True

def drawDice(x,y,n):
  rect(x,y,20,20,TXT)
  rect(x+1,y+1,18,18,SKY)
  txt(str(n),x+5,y+1,PC[5],SKY)

def game():
  global N,DR
  N,Password,Save=menu()
  T=randint(0,N-1)
  DR=randint(1,6)+randint(1,6)
  clean()
  
  A[0]["terrains"].append(1)
  A[0]["terrains"].append(3)
  A[0]["terrains"].append(5)
  A[0]["terrains"].append(6)
  A[0]["terrains"].append(12)
  for i in range(22):
    A[-2][i]=randint(1,5)
  
  draw_terrain()
  print(pay(0,1,9899999,canCancel=True))

  while 1:
    if Password:
      clean()
      txtc(A[T]["name"]+", enter your password:")
      while int(kinput(140,110))!=A[T]["code"]:
        txtc("Wrong password, retry in 3s...",130,PC[4],SKY)
    
    # Dices animation
    wasRewarded=0
    d1,d2=randint(1,6),randint(1,6)
    t=time.monotonic()
    while time.monotonic()-t<0.2:
      drawDice(80,150,randint(1,6))
      drawDice(120,150,randint(1,6))
    drawDice(80,150,d1)
    drawDice(120,150,d2)
    DR=d1+d2
    while time.monotonic()-t<0.2:1
    for i in range(DR):
      t=time.monotonic()
      cid=(cid+1)%40
      if cid==0:
        wasRewarded=20000
      draw_terrain()
      while time.monotonic()-t<0.2:1
    if cid==0:
      wasRewarded=40000
      A[T]["money"]+=20000
    


    clean()
    cid=A[T]["case"]
    a=CASES[cid][1]
    txt("Landed on "+CASES[cid][0],5,5,TXT,SKY)
    if wasRewarded:txtc("You were rewarded by "+str(wasRewarded),202,(0,255,0),SKY)
    draw_case(cid,295,10)
    draw_rent(110,60)

    
    if a==0: # Pay rent/Buy propriety
      if cid in A[-3]:
        c=popup(-1,30,0,0,20,140,[0,(0,255,0),IMP],["Want you buy this propriety?","Price:"+str(CASES[cid][4])],["YES","NO"],[[3],[3]],frame=False)
        if c==0:
          A[-3].remove(cid)
          A[T]["terrains"].append(cid)
      elif cid not in A[T]["terrains"]:
        # find who own the case
        for i in range(N):
          if cid in A[i]["terrains"]:break
        amount=calc_rent(cid,owner=i)
        txtc("You have to pay"+A[i]["name"],30,(255,150,0),SKY)
        txtc(str(amount),50,(255,150,0),SKY)
        while v():1
        while not v():1
        rp=pay(T,i,amount)
        if rp!=0:continue


    elif a==1: # Gain/pay taxe
      amount=CASES[cid][2]
      if amount>0:
        txtc("You have been rewarded by",30,IMP,SKY)
        txtc(str(amount),50,(0,255,0),SKY)
        A[T]["money"]+=amount
      else:
        txtc("You have to pay",30,IMP,SKY)
        txtc(str(amount),50,(255,0,0),SKY)
        rp=pay(T,-1,amount,True)
        if rp!=0:continue

    elif 4: # Chance
      txtc("You draw a chance",30,IMP,SKY)
      drawC(1)
    elif 5: # Chest
      txtc("You draw a community chest",30,IMP,SKY)
      drawC(0)
    
    elif a==6: # Jail
      pass
    elif a==7: # Go to jail
      pass
    elif a==8: # Free park
      pass 
    

    if Save:
      with open(savefile,"w")as f:
        f.write(str(A))
    T=(T+1)%N
  if OS:os.remove(savefile)

while 1:game()

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.