"""Enshittification, by Al Sweigart. Types increasingly large, independently randomized sets of hostile product and interface decisions. Timing automatically compensates when per-character printing takes longer than the requested duration. """ import base64 import lzma import random import re import time # Number of character columns in every displayed row. WIDTH = 100 # Seconds used to type every complete set, regardless of its length. TYPE_DURATION = 3.0 # Seconds to wait between completed sets. SET_PAUSE = 1.0 # Number of sets displayed before starting a new cycle. MAX_SETS = 20 # New examples added per set during the second half of the cycle. LATE_EXAMPLES = 5 # Seconds to wait after the final set before starting over. CYCLE_PAUSE = 5.0 # The supplied catalog is compressed only to keep this downloadable source # file reasonably small. It expands entirely in memory using the standard # library; no files, network access, or third-party modules are required. _EXAMPLE_DATA = """ {Wp48S^xk9=GL@E0stWa8~^|S5YJf5;3skq8eIS&nF?ayQ6eYGTAmuecEa_DBvEMP49&X+x- cSUg#87M<3UiL#%W)n(%FliHw{lX>$jfs`X9yscA4ecgnfofqK_m;qn#r@t~J- Bi|vmH!{bA%+9yM~3)u`QGRi!@HwY45sWcXplt?*sg5U7Neg`+4{AYT%CO4jxPT~JO2(BUq`*AfHdg4V4Xn2UQ4f`V)>fl)^DLu= Ba{+_o<^xQQT9rthNI752BWQAr&- pADSKpPh&hwRB_gfpqHB2)+NZ_kb;f?=Sni(kJyb;(fkyNOjsf)SJAkp5qDM22&Z12I#8UdJjzGGpmK|y3Y(RdA0_xlH8S`fOz#T )W(ZNFs4sGE(Qy=&Hw>}j<_$^EhDc$54DGG6tDe)UUI3IXQRRjz=|8Gh3`T- d%+tLpwwY7DUoskRx0o_Lq;9Q%N`AfNl0lSs&?%B?mw#Yfp_O8<+M?!xNt9nhBv4fM6n}ktgH@U6q&Z+L)?sDgpbZJ?BMT?f{9?G 5eAMervP_Xj3jjR7bWeI03|Vf&I_=DSNN*e>MzD)&i`3#|MR$wcaspCdKFy(tM0x{+q=- GsuKjHVS9(^jnMsKy{ZTCwn}fK|EKA;RI|w@xt=jP>|aewt8&!&v+c@1t$|WgC|9H%)Iuf4#Qrlp%biHRuP1eo`r<=qEPS#Spv)R jw^&^T8jqH|M7(TC9lw@qow`RY<- 5^w7}twy99;?>$RxOKhX*{vgxHZMZYs5QJFu8FkR9BC32QlVRMs>GGzC?%%Gc_X1yQBh5lXaN+BGKyGCGvc-_8JllC$- !p(fF>OqY9rbrxg&jk&7^{2)%_3w!`*- hG|__LCLdeB>CEiZ1QI7^%JkP(w(KDXcfhDHAI3TOf?SpPy%F RKiT3f^|j66iU~G#)5#HUkUZc6JaNF6@(zO74b~Hy|wR5;2n43*!f*;bD8hv!rq10?LUjX?|nbS;Ad+1fVo<$~68aUcz6iC4mGPX %NrnNRYhC^^@eFK$SlP8V0M`dR5vepQRh0>efVe&*wf@=SIbqJ!9>n6n0#5U`M&kgl|a8#*}%{7qwSAD1CCHkQ+v6H5gqG0nnVWJ lpIQY>yq)5Jx1>WKQg;y1LDCdjt-Ld_#$Ovw>kyMeH@?yH7lmfx0$&=t6&hBc=P- k8~nbwNh#fRcP`%qn_E3WOr;dWzb3#3tA2@R+|@GbIARt{z}7CWQ}MIRw(ZL&s^Xde5I6g?&7Y7`kN>D7^t?=zx8{GbOiJg >fyA5Tu3pZ8azSrVM(FZ-Dv)@=IH*hWD&tfQNLVrQNO0Lkga_Ri$hseu;BO%|aRzf0T^vlEVUYGV*|@;yKY!5F;*nHi_w- B^ie%YRBq*kyyIDfYoi$?{K*jSZ@8XY+}i@V!TLlvYO`={MzA1}0t@$M56q@XHl$6$;BilscQ}y(>UGaEowOE{T%$7G3- 1J48J|X3%2oP}#f)z>y49+#bJPTLI2Gf?13OGpZ_wftfX=fGlTtJ%4yFZ;;B)2oy%kiM@Q)c}>jUM1z+tK6pN^5- GajvrFQw*$?uZb79G)Bfjd-p0}m2cNSpb8a{$%3!^J- u%+C&^C`mVYZVL1PA@_Cx=ZPTb86`1!{2@f@aYfY|1XXwgXOo^cIjaiDNh_@FK&Jdk;mp8t4A?}oVj3rZDA1@Z`izDTwz?O43)|N *7retH8RTeO4c<~?bB>haGn_|=Cj|)%_pe3@^Sw7iCfT3$I(l9?m{QkQ#YAiNHtAi!5>0CI`GpO^eI)XXyQ#CtK8Aee9e&XAOJc4 {Dryd4%6S40te2@(<3Ld|8uNXz*cV9?$g|wG(7ghc4ad!AEL}ip}@moGNR`GKAghDn79OPsvzhJ*bT;ohU0CEYo`Klh;o^2mcNaNp%D)F$ ?Appfz@Z--_LIfE<0Bl5LhZ9#wvXcffObxRspam+JXCaBjl4W0R*dl<5IVjVqA1pQqdFR9ip2bfEO3&f^s?TJxv3{WHc#mQ;$G~a !*Xj%Nc+Z}XKh6w>X89MOUBj^z!+(D!|DCArMCUey%@Gwmzv&2W?05@lT!R`w&I3(x%NRhQG~P5bjj;9- &QZMkYWhqJgFWlZdlw6>6Is~Fm_9R_KkQSypWF)5FLmKU$?U{je#T(*j|r!bY}IOL!nW%2!;bi`?+pIE_7M(giVgdFH7|X4dy(Yr i2~Ts#8Swt2MQ>x(v2J1MonJpp}i1(I0mwg1ju6DAGzP7r#j`KJ^r5JGO`q<5PKp#=^1Okl+Go6CW&{#EqFE#aG3I$V>n?QkBnF;Yh2pWDfE!bN@t$jqva{GJ=q2SiL#U0AzBzH#}#wg(DaAX|Qs}Qunc*m}&muWpam7V#ALRU}k``$>8yO0VzeNi5(@HX@}n*9!7pJ>>Z5utAj(0eH1uNDhBRwZ4}{2N&Ub@(DG;TY5P?}{n?zMyg A_>Ui73^lAe&Uwt0>E;SQ&K+EA*BiokF1m`C;u#UDCCsmMj)p2fQ+qCx- 3eUqI`8dx#|5}a@J)X<{!hzyaU4Qdd|g`AQGxOD;!q^-fmq7B`qBkhAtfuy*fbE- n~aE@9ilkdGFojmG#(ws2;CnFn51TCDW(>6`@L- HWxBttD>gb_yFK!&dNv~~HMNfDGCXk0y<2r}=V^k!Sdx)0%vGO|J$yMm9>BWQm42- xaOpJz;@BT_+#^EHU@E9migyv%y*J*iJTQx0hcPLsT82#s_$d01z?=(- QlJuIcqZA4F=nRkZ`>H~%%9eZdWhl&UX2bO{+)n8a{^$h^xb^3dG5XRU;pwT2NhEioF<4xR@y#M;nOoUjp*g!w8;OPh8qmNr0>BcuJtYUyyHR*PZ=C0lxV>PfPnHn?G_x6Niqr15yJ|u#3r3?h We#Tr81Q5rR$VI6;c45NN@m3^b>=_@uTxQHL~$NDji~+cTedwa!LYvtqtqa{T?K@kFUhZ+7_b+0beP?^V~PfF>)7(- r7GjZe`rw{xbcO?>v3@Jcr*C${kGPAF<?)HtfW0)~ABGSr5lidO=L{ADl3dqyP;7}d2@Mpnjw~V9j ^+%qOgU3ng2eDH^zCQC&V=F%xFH@QH%>!uoxmP}Pt$*G=EiyrAihkExn?MeMN|P9+8|Stmi97CrF-jZMC0LrSO!b`Y90%H)o9y~b 6cWn!B4#2Y8R2Sxa!g3WjGvN>o_%v#f}WVm@I$siA6n{&~AUE9{X{_;}>HL5KrDkgQaH=`bU=06qezd`2JZ{%HXC5JHHjXqtIY5Q QXQZj@3Qmpog>hRS#XBiO_Pnw+z_vQm^;`OLqu>To*$rbRzm4c=bvEe;Kyzz;AO|2tN^D!q^nEw~U9SqfFSUB|TD!T>t<8_TDhykHD?~0 0Fih^G*N&)fB-#vBYQl0ssI200dcD """ EXAMPLE_TEXT = lzma.decompress( base64.b85decode("".join(_EXAMPLE_DATA.split())) ).decode("utf-8") EXAMPLES = [ example.strip() for example in re.findall(r"[^.]+(?:\.|$)", EXAMPLE_TEXT) if example.strip() ] def shuffled_examples(): """Return a freshly shuffled copy of the full example catalog.""" examples = EXAMPLES[:] random.shuffle(examples) return examples def select_and_wrap_examples(count): """Select count examples, preferring exact fits before hard splitting.""" available = shuffled_examples() output = [] column = 0 for _ in range(count): if not available: available = shuffled_examples() if column == WIDTH: output.append("\n") column = 0 example_index = 0 separator_length = 1 if column else 0 if column + separator_length + len(available[0]) > WIDTH: for index, candidate in enumerate(available): if column + separator_length + len(candidate) == WIDTH: example_index = index break example = available.pop(example_index) addition = (" " if column else "") + example for character in addition: if column == WIDTH: output.append("\n") column = 0 output.append(character) column += 1 return "".join(output) def type_set(text, average_characters_per_tick, adapt_timing): """Type one set and return its updated average batch size.""" if TYPE_DURATION <= 0: print(text, end="", flush=True) return average_characters_per_tick character_delay = TYPE_DURATION / max(1, len(text)) typing_started = time.perf_counter() character_index = 0 character_accumulator = 0.0 while character_index < len(text): time.sleep(character_delay) character_accumulator += average_characters_per_tick batch_size = max(1, int(character_accumulator)) character_accumulator -= batch_size print(text[character_index:character_index + batch_size], end="", flush=True) character_index += batch_size actual_typing_duration = time.perf_counter() - typing_started if adapt_timing and actual_typing_duration > TYPE_DURATION: average_characters_per_tick *= actual_typing_duration / TYPE_DURATION return average_characters_per_tick def main(): """Run the endless animation.""" while True: first_half_sets = (MAX_SETS + 1) // 2 average_characters_per_tick = 1.0 example_count = 0 for set_number in range(1, MAX_SETS + 1): example_count += 1 if set_number <= first_half_sets else LATE_EXAMPLES text = select_and_wrap_examples(example_count) average_characters_per_tick = type_set( text, average_characters_per_tick, adapt_timing=set_number > 1, ) print("\n") # End this set and leave one blank row after it. if set_number < MAX_SETS: time.sleep(SET_PAUSE) time.sleep(CYCLE_PAUSE) if __name__ == "__main__": try: main() except KeyboardInterrupt: pass