Python 100 line Challenge - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: Python 100 line Challenge (/thread-37322.html) |
Python 100 line Challenge - codingCat - May-27-2022 The goal of the 25 line challenge was to see how much could be squished into a very minimal space. It was amazing to see how low people could go. I was particularly impressed with menator01's managing to cram Rock Paper Scissors into five lines. I am amazed with out much I learned about the workings of Python. But 25 line is too limiting. The challenge became a practice in obfuscating code, and all but eliminated the possibility of graphics and games. As a teacher I want keep the examples small enough that a student can grasp the ideas contained in the code, while still leaving room to make the code itself comprehensible. Hence the expansion of the challenge to 100 lines. Genius arises when you have a good idea, not enough resources. RE: Python 100 line Challenge - codingCat - May-27-2022 Here is a first offering. Something I have dubbed a chaos spinner. Credit for the trig needed to rotate the spinners goes to its original author. My contribution was randomization and having it reset periodically... and of course converting it to Python. Enjoy. #Choas Spinner - Original code by Math Man in SmallBasic - Randomizing update by codingCat aka Matthew L. Parets for the 50 line challange. Python conversion in 2022. from tkinter import * #tools to build the window from random import * #tools for choosing random numbers from time import * #tools for checking the time, and pausing the app from math import * #tools for sin,cos #--Record when a key is pressed def onKeyDown(event): global lastkey, appOn lastkey = event.keysym if lastkey == "Escape" : appOn = False #Close app when escape key is pressed #--Allow for a graceful exit def onShutdown(): global appOn appOn = False win = Tk() #Build a window win.title('Chaos Spinner') #Set the title win.geometry("640x480") #default window size win.state('zoomed') #maximize the window win.config(bg="#000000") #background color of black win.update() #update the window to show the user winwid = win.winfo_width() #get the resulting height and width for calculations winhei = win.winfo_height() canv = Canvas(win, width = winwid, height = winhei, bg="black") #build a canvas to draw on canv.pack() #add the canvas to the window win.bind("<KeyPress>", onKeyDown) #set the key down event win.protocol("WM_DELETE_WINDOW",onShutdown) #Set the shut down event appOn = True #run the program while true while appOn: #keep going until canceled by the app window closeing lastkey = "" #reset the key press, changes design when space is pressed amount = randint(4,16) #choose the number of segments in the pen total = 0 #track the length of the pen angle = [] #reset the lists that describe the pen and its location dist = [] rate = [] x = [] y = [] angrng = randint(1,12) * 15 #occationally limit the angle range to allow for more sweeping shapes for i in range(0,amount): #for each pen segment angle.append(randint(-angrng,angrng)) #Randomly choose the speed around the circle for each pen segment dist.append(randint(16,round((winhei/1.1)/amount))) #randomly choose the distance from the center - dividing by 1.1 ensures that most will stay on the screen total = total + dist[i] #keep track of the distance rate.append(randint(-10,10) * 2) #change speed of the angle for i in range(0,amount): #randomly distribute what is left of distance from center to edge rndpos = randint(0,amount-1)#random position dist[rndpos] = dist[rndpos] + (((winhei / 1.1) - total) // amount) x.append(winwid // 2) #place the pen in the center of the window y.append(winhei // 2) for i in range(1,amount): #for each segment of the pen x.append(round(x[i-1]+dist[i]*cos(radians(angle[i])))) #set its location based on its angle and distance from middle y.append(round(y[i-1]+dist[i]*sin(radians(angle[i])))) x.append(x[amount-1]) #duplicate the last entry. This will help us draw connecting lines y.append(y[amount-1]) pen = [] #list to hold the lines of the pen for i in range(0,amount): pen.append(None) #fill it with lots of nothing line = [] #list to hold the connecting lines and point circles while appOn and lastkey != "space" and (time_ns() // 100000000) % 150 != 0 : #while window is open, the space has not been pressed, and if seconds has not elapsed for i in range(1,amount): #for each pen segment if pen[i] != None: #Is there already a pen segment here? canv.delete(pen[i]) #delete the old pen segment angle[i] = angle[i]+rate[i] #Calcualte the position of the new pen segment x[i] = x[i-1]+dist[i]*cos(radians(angle[i])) y[i] = y[i-1]+dist[i]*sin(radians(angle[i])) pen[i] = canv.create_line(x[i-1],y[i-1],x[i],y[i],fill="green") #create the new pen segment color = ["#"+''.join([choice('ABCDEF0123456789') for i in range(6)])] #choose a random color (random hex value) line.append(canv.create_line(x[amount-1],y[amount-1],x[amount],y[amount], fill=color)) #draw a line between the end of the pen, and the ends last location x[amount] = x[amount-1] #update the end of the pen y[amount] = y[amount-1] line.append(canv.create_oval(x[amount-1]-3,y[amount-1]-3,x[amount-1]+3,y[amount-1]+3, outline=color)) #draw a circle at the end of the pen canv.update() #update the window so the user can see the change sleep(0.025) #sleep for a tick to let other stuff happen in the OS for s in line: canv.delete(s) #delete all of the connecting lines and circles for l in pen: canv.delete(l) #delete the old pen #once the window has been closed, clean up all of the resources and close the window. win.destroy() RE: Python 100 line Challenge - codingCat - May-28-2022 Another offering. This one is a little reflex game. Again, showing how expanding the challenge to 100 lines provides a lot more flexibility using GUI objects while keeping the code easy to follow. #Stop it! Originally developed for Smallbasic in May 2016, converted to Python in May of 2022. (C)opyright codingCat AKA Matthew L. Parets -- Released under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. #Hit the space bar to stop the ball. The closer you get to the middle ofthe basket, the higher your score. import tkinter, time #functions and events ---------------------------------------------------------------------------------------------------- def SpeedIncrease(): #At the end of every frame, decrease frame size and speed up global speed,framelength,framestart if time.time_ns() // 1000000 - framestart > framelength: #Have we reached the end of the frame? if framelength > 1: #Frames can only get so short, stop at 1 millisecond speed = speed * 2 #Double the speed if speed > 0.7: #Not too fast. speed = 0.7 #Set to half a pixel per frame framelength = framelength * 0.80 #Decrease the length of frame by 20% framestart = time.time_ns() // 1000000 #Start the next frame. def DisplayFinishfinishMsg(): #Display the win/lose message canvas.create_line(ballx,0,ballx,winhei, fill="#dddddd", width=2, dash=(2, 1,1,1)) #highlight the location of the of the ball canvas.create_line(ballx+ballsize,0,ballx+ballsize,winhei, fill="#dddddd", width=2, dash=(2, 1,1,1)) scoreMsg = 0 finishMsg = "" if ballx + ballsize < targetX: #Stopped before the basket finishMsg = "Too Soon!" scoreMsg = 0 elif ballx > targetX + targetWidth: #Stopped after the basic finishMsg = "Missed it!" scoreMsg = 0 elif ballx < targetX and ballx + ballsize > targetX: #Stoped just inside finishMsg = "Just Made it!" scoreMsg = round(1000 * (ballsize - (targetX - ballx))) elif ballx + ballsize > targetX + targetWidth and ballx < targetX + targetWidth: finishMsg= "Almost Missed it!" #Stopped almost outside scoreMsg = round(1000 * (ballsize - ((ballx + ballsize) - (targetX + targetWidth)))) else: #Nailed it - fully inside finishMsg = "Bulls Eye" scoreMsg = chr(8734) scoreMsg = "Score = " + str(scoreMsg) canvas.create_text(4,25, text = finishMsg, anchor="nw", font=("Tahoma",65,"bold","italic"),fill="#333333") canvas.create_text(0,25, text = finishMsg, anchor="nw", font=("Tahoma",65,"bold","italic"),fill="green") canvas.create_text(4,winhei - 25, text = scoreMsg, anchor="sw", font=("Tahoma",65,"bold","italic"),fill="#333333") canvas.create_text(0,winhei - 25, text = scoreMsg, anchor="sw", font=("Tahoma",65,"bold","italic"),fill="blue") #** Key press event - Update the last key variable def onKeyDown(event): global lastkey lastkey = event.keysym #** Shut down event. Called when window is closed. Having this event prevents the window itself from closing #** until the main processing loop stops, and the destroy function is called. def onShutdown(): global gameon gameon = False #Initial setup --------------------------------------------------------------------------------------------------------------- winwid, winhei = 800,500 #The size of the window win = tkinter.Tk() #build the window win.title("Stop it") #give the game a name win.geometry(f"{winwid}x{winhei}+0+0") #set the size and location of window -- looking for a super power? Google "python fstrings" lastkey="" #the last key pressed, set in the key down event ballsize = 50 #size of player ballx, bally = 0,winhei / 2 - ballsize/2 #location of player (centered to the left) targetSize = ballsize * 1.25 #the size and location of the target targetWidth = targetSize * 1.125 targetX = winwid - targetSize * 2.5 targetY = winhei/2 - targetSize/2 speed = 0.00075 #player speed at the start of the game (very slow) framelength = 500 #length of the frame at start of game (very long) gameon = True #game is on until the window is closed canvas = tkinter.Canvas(win,bg="#ffffcc",width=800, height=600) #add a work area to the window canvas.pack(expand=True) ball= canvas.create_oval(ballx,bally, ballx+ballsize,bally+ballsize, outline="#bb0000", fill="red", width=3) #add the player and target targetX + (targetWidth / 2) - (ballsize / 2) target = canvas.create_rectangle(targetX,targetY, targetX+targetWidth,targetY+targetSize, outline="#0000bb", fill="blue", width=3) win.bind("<KeyPress>", onKeyDown) win.protocol("WM_DELETE_WINDOW",onShutdown) # Main processing / Game loop. Repeats once per frame. --------------------------------------------------------------------- framestart = time.time_ns() // 1000000 #current millisecond count. Why note nanoseconds? Why divide by 1000000? Because no one wants to deal in billions of a second secstart = time.time_ns() // 1000000 while gameon and lastkey != "space" and ballx < winwid - ballsize: #continue until - the window is close, the space bar is pressed, or the ball reaches the far end of the screen ballx = ballx + speed #update the balls location canvas.coords(ball, ballx,bally, ballx+ballsize,bally+ballsize) #move ball to new location SpeedIncrease() #increase the speed, ball goes a little faster each round win.update() #update the graphics in the window (does not happen automatically) DisplayFinishfinishMsg() while gameon: #hold the window open until the player closes it win.update() time.sleep(0.25) #** Main processing loop is done, close the window win.destroy() RE: Python 100 line Challenge - Larz60+ - May-28-2022 You might like this: https://python-forum.io/thread-37300-post-157645.html#pid157645 RE: Python 100 line Challenge - codingCat - May-29-2022 (May-28-2022, 09:56 PM)Larz60+ Wrote: You might like this: https://python-forum.io/thread-37300-post-157645.html#pid157645 I have recently seen this... its a great video. I love gravity simulators. It hadn't occurred to me to convert it to python. i will have to take a look at the code. I should be able to adapt Chaos Spinner to it without too much trouble. :-) RE: Python 100 line Challenge - Coricoco_fr - May-29-2022 (May-27-2022, 02:19 PM)codingCat Wrote: Here is a first offering. Something I have dubbed a chaos spinner. Credit for the trig needed to rotate the spinners goes to its original author. My contribution was randomization and having it reset periodically... and of course converting it to Python. Hello, This is not well written tkinter code. With tkinter you use a callback (use after()) to loop... I see other clumsinesses... RE: Python 100 line Challenge - codingCat - Jun-01-2022 (May-29-2022, 06:06 PM)Coricoco_fr Wrote: Hello, I actually developed this code to specifically avoid using after. When teaching coding to new students, something like an event call back can be completely incomprehensible. By using a while loop and leaning into update() and sleep() it is much easier to see the flow of the code. Would you be interested in restructuring and reposting the code to use after() rather than the while loop? I would love to see your take on the event driven design, as well as seeing the fixes to the other perceived clumsiness's. RE: Python 100 line Challenge - menator01 - Jun-04-2022 Here is my go at it using after. import tkinter as tk import time class View: def __init__(self, parent): self.parent = parent self.canvas = tk.Canvas(self.parent, bg='#ffffcc') self.canvas.pack(expand=True, fill='both') self.ballx, self.bally = 2, 275 self.ball_size, self.targetsize = 50, 50 * 1.25 self.targetx, self.targety = 800 - self.targetsize * 2.5, 300 - self.targetsize/2 self.target = self.canvas.create_rectangle(self.targetx, self.targety, self.targetx+self.targetsize*1.125, self.targety+self.targetsize, outline='#0000bb', fill='blue') self.ball = self.canvas.create_oval(self.ballx, self.bally, self.ballx+self.ball_size, self.bally+self.ball_size, outline='#bb0000', fill='red', width=3) self.text = self.canvas.create_text(10, 15, font=('tahoma 50 bold'), anchor='nw') self.text2 = self.canvas.create_text(10, 540, font=('tahoma 50 bold'), anchor='w') class Controller: def __init__(self, view): self.view = view self.view.parent.bind('<space>', self.ballstop) self.counter, self.speed = 0, 0.2 self.key = False self.msg = None self.color = None self.score = None self.view.btn = tk.Button(self.view.parent, text='Reset', command=self.reset) self.view.btn.pack_forget() self.update() def update(self): if self.key != True: if self.counter > 1000: self.speed = 1.35 self.counter += 1 self.view.ballx = self.view.ballx + self.speed if self.view.ballx > 725: self.view.ballx = 725 self.view.canvas.coords(self.view.ball, self.view.ballx, self.view.bally, self.view.ballx+self.view.ball_size, self.view.bally+self.view.ball_size) if self.counter == 1400: self.msg = 'Missed It!' self.score = 0 self.color = 'red' self.key = True self.view.btn.pack(side='bottom') else: if self.view.ballx + self.view.ball_size < self.view.targetx: self.msg = 'Too Soon!' self.score = 0 self.color = 'red' elif self.view.ballx > self.view.targetx + self.view.targetsize: self.msg = 'Missed It!' self.score = 0 self.color = 'red' elif self.view.ballx < self.view.targetx and self.view.ballx + self.view.ball_size > self.view.targetx: self.msg = 'Just Made It!' self.score = 50 self.color= 'orange' elif self.view.ballx + self.view.ball_size > self.view.targetx + self.view.targetsize and self.view.ballx < self.view.targetx + self.view.targetsize: self.msg = 'Almost Missed It!' self.score = 50 self.color = 'orange' elif self.view.ballx + self.view.ball_size > self.view.targetx and self.view.ballx + self.view.ball_size < self.view.targetx + self.view.targetsize: self.msg = 'Bulls Eye!' self.score = 100 self.color = 'blue' self.message(self.msg, self.color, f'Score: {self.score}') self.view.parent.after(1, self.update) def reset(self): self.key = False self.view.canvas.itemconfigure(self.view.text, text='') self.view.canvas.itemconfigure(self.view.text2, text='') self.view.ballx, self.speed, self.counter = 2, 0.2, 0 self.view.btn.pack_forget() self.msg, self.score = None, None def ballstop(self, event): if event.keysym: self.key = True self.view.btn.pack(side='bottom') def message(self, msg, color, score): self.view.canvas.itemconfigure(self.view.text, text=msg, fill=color) self.view.canvas.itemconfigure(self.view.text2, text=score, fill=color) if __name__ == '__main__': root = tk.Tk() root.geometry('800x600+200+200') controller = Controller(View(root)) root.mainloop() RE: Python 100 line Challenge - menator01 - Jun-08-2022 One more approach using after. It's a shot though. import tkinter as tk def window(): window.canvas = getattr(window, 'canvas', tk.Canvas()) window.canvas.pack(fill='both', expand=True) window.canvas.configure(bg='ivory') window.canvas.update() window.width = window.canvas.winfo_width() window.height = window.canvas.winfo_height() window.font = ('times 30 bold') def target(): target.center = getattr(target, 'center', (window.width-150, round(window.height/2))) target.linex = window.canvas.create_line(0, target.center[1], 800, target.center[1], fill='black') target.liney = window.canvas.create_line(target.center[0], 0, target.center[0], window.height, fill='black') target.target = window.canvas.create_rectangle(target.center[0]-30, target.center[1]-30, \ target.center[0]+30, target.center[1]+30, fill='blue') def ball(): ball.ballx = 30 ball.speed = 0.2 ball.ball = window.canvas.create_oval(ball.ballx-25, target.center[1]-25, \ ball.ballx+25, target.center[1]+25, fill='red') def ballstop(stop=False): ballstop.msg = getattr(ballstop, 'msg', None) ballstop.score = getattr(ballstop, 'score', None) ballstop.stop = stop if round(ball.ballx)+25 < target.center[0]-30: ballstop.msg = 'Too Soon!' ballstop.score = 0 elif round(ball.ballx)-25 > target.center[0]+30: ballstop.msg = 'Missed It!' ballstop.score = 0 elif round(ball.ballx)-25 < target.center[0]-30 and round(ball.ballx)+25 > target.center[0]-30: ballstop.msg = 'Just Made It!' ballstop.score = 50 elif round(ball.ballx)-25 < target.center[0]+30 and round(ball.ballx)+25 > target.center[0]+30: ballstop.msg = 'Almost Missed!' ballstop.score = 50 else: ballstop.msg = 'Bulls Eye!' ballstop.score = 100 def update(): ball.ballx = ball.ballx + ball.speed root.bind('<space>', lambda stop: ballstop(stop=True)) if round(ball.ballx) >= 200: ball.speed = 1.35 if round(ball.ballx) == 750: ball.ballx = 750 ballstop.msg = 'Missed' ballstop.stop = True window.canvas.coords(ball.ball, ball.ballx-25, target.center[1]-25, ball.ballx+25, target.center[1]+25) if ballstop.stop != True: root.after(1, update) else: color = 'red' if ballstop.score == 0 else ('orange' if ballstop.score == 50 else 'blue') window.canvas.create_text(10, 30, font=window.font, text=ballstop.msg, fill=color, anchor='w') window.canvas.create_text(10, window.height-100, font=window.font, text=f'Score: {ballstop.score}', fill=color, anchor='w') root.after_cancel(root) root = tk.Tk() root.geometry('800x600+200+200') root.resizable(False, False) window() target() ball() ballstop() update() root.mainloop() with reset button import tkinter as tk def window(): window.canvas = getattr(window, 'canvas', tk.Canvas()) window.canvas.pack(fill='both', expand=True) window.canvas.configure(bg='ivory') window.canvas.update() window.width = window.canvas.winfo_width() window.height = window.canvas.winfo_height() window.font = ('times 30 bold') window.button = tk.Button(window.canvas, text='Reset') window.button.pack_forget() def target(): target.center = getattr(target, 'center', (window.width-150, round(window.height/2))) target.linex = window.canvas.create_line(0, target.center[1], 800, target.center[1], fill='black') target.liney = window.canvas.create_line(target.center[0], 0, target.center[0], window.height, fill='black') target.target = window.canvas.create_rectangle(target.center[0]-30, target.center[1]-30, \ target.center[0]+30, target.center[1]+30, fill='blue') def ball(): ball.ballx = 30 ball.speed = 0.2 ball.ball = window.canvas.create_oval(ball.ballx-25, target.center[1]-25, \ ball.ballx+25, target.center[1]+25, fill='red', tags=('ball',)) def ballstop(stop=False): ballstop.msg = getattr(ballstop, 'msg', None) ballstop.score = getattr(ballstop, 'score', None) ballstop.stop = stop if round(ball.ballx)+25 < target.center[0]-30: ballstop.msg = 'Too Soon!' ballstop.score = 0 elif round(ball.ballx)-25 > target.center[0]+30: ballstop.msg = 'Missed It!' ballstop.score = 0 elif round(ball.ballx)-25 < target.center[0]-30 and round(ball.ballx)+25 > target.center[0]-30: ballstop.msg = 'Just Made It!' ballstop.score = 50 elif round(ball.ballx)-25 < target.center[0]+30 and round(ball.ballx)+25 > target.center[0]+30: ballstop.msg = 'Almost Missed!' ballstop.score = 50 else: ballstop.msg = 'Bulls Eye!' ballstop.score = 100 def update(): ball.ballx = ball.ballx + ball.speed root.bind('<space>', lambda stop: ballstop(stop=True)) if round(ball.ballx) >= 200: ball.speed = 1.35 if round(ball.ballx) == 750: ball.ballx = 750 ballstop.msg = 'Missed' ballstop.stop = True window.canvas.coords(ball.ball, ball.ballx-25, target.center[1]-25, ball.ballx+25, target.center[1]+25) if ballstop.stop != True: root.after(1, update) else: color = 'red' if ballstop.score == 0 else ('orange' if ballstop.score == 50 else 'blue') window.canvas.create_text(10, 30, font=window.font, text=ballstop.msg, fill=color, anchor='w', tags=('msg',)) window.canvas.create_text(10, window.height-100, font=window.font, text=f'Score: {ballstop.score}', fill=color, anchor='w', tags=('score',)) window.button.pack(side='bottom', pady=10) window.button['command'] = reset root.after_cancel(root) def reset(): window.button.pack_forget() window.canvas.delete('all') window() target() ball() ballstop() update() root = tk.Tk() root.geometry('800x600+200+200') root.resizable(False, False) window() target() ball() ballstop() update() root.mainloop() RE: Python 100 line Challenge - Coricoco_fr - Jun-20-2022 (Jun-04-2022, 10:25 AM)menator01 Wrote: Here is my go at it using after. (Jun-08-2022, 09:52 AM)menator01 Wrote: One more approach using after. It's a shot though. Hello. The use of getattr is superfluous. It makes the code unnecessarily heavy... |