From 712df01465958037a642cd5d24bf30069996a459 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 10 Jun 2026 21:58:50 +0200 Subject: [PATCH] added a vipe coded tetris gui --- gpu.py | 128 ------------------------- tetris_gui/demo | Bin 0 -> 17144 bytes tetris_gui/demo.c | 99 ++++++++++++++++++++ tetris_gui/gpu.h | 91 ++++++++++++++++++ tetris_gui/gpu.py | 182 ++++++++++++++++++++++++++++++++++++ tetris_gui/install.sh | 26 ++++++ tetris_gui/requirements.txt | 1 + 7 files changed, 399 insertions(+), 128 deletions(-) create mode 100755 tetris_gui/demo create mode 100644 tetris_gui/demo.c create mode 100644 tetris_gui/gpu.h create mode 100755 tetris_gui/gpu.py create mode 100755 tetris_gui/install.sh create mode 100644 tetris_gui/requirements.txt diff --git a/gpu.py b/gpu.py index d150cca..c5cb8ca 100644 --- a/gpu.py +++ b/gpu.py @@ -1,6 +1,5 @@ import random from PIL import Image -import math n0 = [ [1,1,1], @@ -82,62 +81,6 @@ n9 = [ [1,1,1] ] -# a_a = [ -# [1,1,1], -# [1,0,1], -# [1,1,1], -# [1,0,1], -# [0,0,0] -# ] - -# a_b = [ -# [1,1,0], -# [1,1,1], -# [1,0,1], -# [1,1,1], -# [0,0,0] -# ] - -# a_c = [ -# [1,1,1], -# [1,0,0], -# [1,0,0], -# [1,1,1], -# [0,0,0] -# ] - -# a_e = [ -# [1,1,1], -# [1,1,0], -# [1,0,0], -# [1,1,1], -# [0,0,0] -# ] - -# a_o = [ -# [1,1,1], -# [1,0,1], -# [1,0,1], -# [1,1,1], -# [0,0,0] -# ] - -# a_r = [ -# [1,1,1], -# [1,0,1], -# [1,1,0], -# [1,0,1], -# [0,0,0] -# ] - -# a_s = [ -# [1,1,1], -# [1,0,0], -# [0,1,1], -# [1,1,1], -# [0,0,0] -# ] - ### FONT: https://github.com/teryror/pixel-fonts/tree/master a_s = [ @@ -240,19 +183,6 @@ class color: r = other[0] g = other[1] b = other[2] - # if self.r: - # new_r = (self.r + r) >> 1 - # else: - # new_r = self.r - # if self.g: - # new_g = (self.g + g) >> 1 - # else: - # new_g = self.g - # if self.b: - # new_b = (self.b + b) >> 1 - # else: - # new_b = self.b - # return (new_r, new_g, new_b) return (((self.r * r) >> 8), ((self.g * g) >> 8), ((self.b * b)>>8)) def shiftr(self, n): return ((self.r >> n), (self.g >> n), (self.b >>n)) @@ -314,11 +244,6 @@ def make_score(): score = random.randint(0, 999999999) return score -# red = color(255, 0, 0, 255) -# blue = color(0, 0, 255, 255) -# green = color(0, 255, 0, 255) -# purple = color(160, 32, 240, 255) -# orange = color(255, 165, 0, 255) black = color("#000000") white = color("#ffffff") @@ -337,10 +262,6 @@ sky_blue = color("#0080ff") purple = color("#9d00ff") lime = color("#5fdf5f") - - - - colors = [ black, cyan, @@ -417,15 +338,6 @@ for y in range(height): pixels[x, y] = colors[board[yd>>5][xd>>5]].mask(0xFFFFFFD7) else: pixels[x, y] = colors[board[yd>>5][xd>>5]].mask(0xFFFFFFC0) - # if (xs == 31) or (ys == 31): - # if (xs == 0) or (ys == 0): - # pixels[x, y] = colors[board[yd>>5][xd>>5]].shiftr(1) - # else: - # pixels[x, y] = colors[board[yd>>5][xd>>5]].shiftr(2) - # elif (not xs) or (not ys): - # pixels[x, y] = colors[board[yd>>5][xd>>5]].get() - # else: - # pixels[x, y] = colors[board[yd>>5][xd>>5]].mask(0xFFFFFFC0) ### Next Piece elif x>=nb1.x and x=nb1.y and y=sn2.x and x=sn2.y and y=b2.x and x=b2.y and y>5][xd>>5]].multiply(sprite_pixels[yd&0b0000000000011111, xd&0b0000000000011111]) - # ### Next Piece - # elif x>=nb2.x and x=nb2.y and y>5][xd>>5]].multiply(sprite_pixels[yd&0b0000000000011111, xd&0b0000000000011111]) - ####################### BG else: if (x+y)&1: pixels[x, y] = (0, 0, 255) else: pixels[x, y] = ((x+y)>>3, (((1280+720)>>3)-((x+y)>>3)), 0) -# offset = 0 - -# numbers = [a_s, n0, n1, n2, n3, n4, n5, n6, n7, n8, n9] -# for num in numbers: -# for y in range(len(num)<<2): -# for x in range(len(num[y>>2])<<2): -# if num[y>>2][x>>2]: -# pixels[x+offset, y] = white.get() -# else: -# pixels[x+offset, y] = black.get() -# offset += (len(num[0])<<2)+4 - -# letters = [a_s, a_c, a_o, a_r, a_e] -# offset = 0 - -# base_offset = 720-32 - -# for letter in letters: -# for y in range(len(letter)<<2): -# for x in range(len(letter[y>>2])<<2): -# if letter[y>>2][x>>2]: -# pixels[x+offset, y+base_offset] = white.get() -# else: -# pixels[x+offset, y+base_offset] = black.get() -# offset += (len(letter[0])<<2) + 4 - - - img.save("output/alpha.png") img.show() diff --git a/tetris_gui/demo b/tetris_gui/demo new file mode 100755 index 0000000000000000000000000000000000000000..15c0c160e09934e7ed690d2d8a8f03fe5a840891 GIT binary patch literal 17144 zcmeHOeRN#Kb)Q{Xw)n6muuW`i@Zt|*6TFs*Mf|ayT?~LYiZGa#}-6Yg}ViHdumFTA6SXrEz-{jtHwnP&Fv6LXEe- zJ2Us`?Xx!Ffwg@v>j8fZS|`>$?kf)DNFG@vz#g>nqpq zwYuM#>TZ;MFa?!S_ua8!o49ZDzMEdY{@v?VO#MaTyG?&^Q)K=JA1sPS8kQ|x6b&tm zMq=rB21EnK$L+Zy*S5mfAD_)&SX-Kt<&8{s$qD$BPLxC_(efavE;*8><0KMdUC zR0iLG-)Q>OoYC+|89Y%2_m{!n0PexhndSi)t==z`!MB&e?=OS@5V!|FXIc+nwi5(@ zHGZSD=QiM%i7Q3khYS*rLlFqHrh<+41sa?03p52IQPGqPhecCUG~L=P(yh^OI3Zes z(P+F8su&=Rq@PR$T7r?7XibH}$)spXBqOm@6BHWTf`O(;EEtVE7#4|mA{-M49V<6&6$+4X7UB4@WNh0dfYs0H;IFDy~_Uce7sZWs!ATE`a*D9Z42mGI1pb#J@JaR6pBkBgDkEFju}BDGUq0n7 zb{m;Ds!qw46mNVRaB<#0;95P)haBI(=6q@j3TV*6y=aFIy8)~HR9^os=x#$9=-8UqDO80&KVK{wrhnMT>>U>A2}gm6 zU#>B7Q)f;>ZMn*;h?qYz%+A7dQuU;fg|=BENtT>XPgYQ8VfeFRQI#-`ptnxxx^^1o z-Y&yT=M8h)QNvt+WS!|h42>=W1yJQwWUlTfSQi;)$1&>D6EtuNH=)W-e-AaP|1d6@ zy*(mznX2ye>$C+eyn#9y_!;-??e0hoUUz`qF#u4OI^E(%0aJd%W*4*a2+Rt>$MK~k>jb$@)5c_*5=uAIL*}+YbNv8(5N)^O zI{D#cXtZdn#r2|xugm&}$_!x45tq7U(6ZYG%qfo?+`$t}E!tf$(|zW)UUU7~)uz8^ zwYmPiPXDls|81TAb276ayRA3tKbO1Jo!!=lhUS(c^B$Zlm}ksSsj+us9Ay0GT+mwK z@7eQD8UHZy!z=t}_Z&Mo5~ukW)JJm$W|YXgPt9HXq|xyUkLzT|r*5;O<2SPwyltN8 z_*un*GYh&f#WbEYa#P-v!O2yAVFC5=I5$m#oqjc1zKj64@)#JIybG%Pq$*88uI_9N z?rv(m*0@mGCtd2XyOhBkYK@r}Y)*Kz6Qq31hia@Y+c`{wv+wtCXwCc!9O{1@MStGX zEYB{r`Q3$4sPFp@YOLYvU_BY5r8eC`=r+D<|h)d!;J zXDbws3=Cpf5kg1IL-R3c{lkm%h4&By!`wIjHz1kUh=}xcMz->WFG3|(_Z5^&x7Ab% zoP1b4{-1dI$@R{2Sj8B*eMiU?gX%0zcDa4sM8LkISd!MiO8WIjX;tznjKneg#0h#2 zK$=(M8KKLmf0b5F^+!>wkVePI@lD!(1xu;1_6HI?5EZ`kj{c1`6s#aj7Iyyf>p(3JhaO@QCjh+{H3 zlq0{pRC%@N?-Lx0-O9rRVKQ*g3rp-Z!i-#w)Q#LTa?s>nIYL}k4#u@6mS3HP@B9ZI znEPe|M>V2)%08NxVk%r-I#YV{dSrA zJP1=ZM_028OT7hM3r?EPNbnhopb6z!9fhl0Co}I)fT%l@cbog>l94pU?88%Su5uL| z$l^Jq>|Y@*j<4Beo{?joI_QjLp4;T2X}?V4I_TNZVRkR*LU&WIo2L!)$AvG;czoW` z@YE0{c^MN|<~4F@MfHJ?X!cNJhT1b7^O;=08rf~bA>&I^oY(*M^8xJv#TaA6c4(9ojhcuN?USS-AI(WYel-f&~8bx|suN=8}(yV8+GyAo+{ zVz>DE?1sg&Z~2;-7ZN+JpWU#8^2o#_NEwCfrAHwrns(_&iStBhVz4LHA)NESP0seL zYqukeaV~=~rflz&6VEz!8RglArb|jR#ZjK?rcEzRjWm(czw^Q&Pb%(-2Gg;|W)EUW zadFM4Sg{k7{@?J#$Hn5)pzHpvSlk2ZA1oG+gT4exo8NmdBhae%Pe5m)QO7}V1-%@P zhwY$`g6;vm1J9A?K{sRdavYTY*G$Rv;AY`!t8vY^bVAiZjn4$$g?g_gh1C<}zbob< zyA;2{kBY@SsF+l#dD4!`>Uh%yOQXu>1@w@wzVzCa?vTu{-Qhpq8E9CR+@|`2{v%uRRzuPW% z-(}T*4)|X}o+y#uXUS(G4(B0%vqYY^>>u!I%vDs; z|4b=W)Qqo~=ElF7YD7iVB^4-!M$+gq8jDmXHI=?QaJP#*VdNSmmvEKvZU;}g)K1bT zdrE}SZ!C>PU@QV-5g3cWSOmr*FcyKa2>j1SfWKqo@78!7w^vt9Z&zfZMH>>mm!ULx zm7>!XvA&O+p?P{mMCnRJtncJ#y-SJZzdm1#6W^sbK(wx;bVz4ttxIW8%XuI8k%Fyn ztfpz6*1Ptk%|tCnYs9!tE`x@G3QKIiWDYfYaGCr&u!tHL?_WZ3I*N>Na(jOEd zRq>Wx^76eaKrF*0&vC}KO!5bG`FKYodG?dq@_&)|`*CO8%k(%{qv_q6Hfeglru#Mh zmZsm=^lvpit?7?6b=K4Db0Ugw_b7Tu)4Zm=n)3IK{Cy*T&v?hGRkwKNW5=<;vv`?z zv3Id&N$rwlwM%cPRpphtx*!-Q{$0LJwd+v7yToL{-?4kNa5Cs<`m=z$@#SAl$w!Ss zHkWXp1E;++;e21};>DfFxNui>`4mF!qeOdG7j}F6J*ot8q5GK1;rpC%6B!qF_558c zS0IBlPZ2@i zMU(y}jmy0_upevu`x;MZecH3T#3a$~QGh0*PveIi_<+Xq4tz-Cy$+oA{8YX*p1@NY z=TbP2FONj=O{d0ryfHpS;?qUDPbn_f`ZKit&TACF^Wq%f6o)JHMD(WCU!?J$YFxfc zfb3@AqxIJ+;4bX66LWL{HZq$2T~hxF;k@tf0gf+u1fQSaVp^pBG;3aEJOf-01%@Pj zx(xmja8HS*VisYd_K4|Xr>|7_4)kk)A9djG0Uxd24+(cqw4PU7+DB#dt5EM~<;*C9 zF9c5Qaklf8GWr{Ud$-^TWTKqk&pLggV(fU8v4?OwK3O27uTpQa+XBqsTfs?<^e*8IbkIml7;z2&! zVn#AABYU~~V)asK1Qs)(KUzO}fscA_VoPH(wb&cS;e%j9B#;X35&#--JR!CP=*UTh zK&mBx6A-a*D^x@Az^-V#As7vWQt@PKAee3wjq#R5G@J^DytSii(1D6bAec-BcL&0; zRB|_tXarlrfl#`oWj9Q0TmYJ>64g++CGKq$c=Ib|GLue(f;fFam^z!$7!3!Lf{te- z@!DN%TI*ls4{X(Rou+qcx<%7_G~KA_T16pVwQ=3X&09nw8f**)5|MBt&XYu<(LgNR zmZB(yV4oaa+4DGrnT)&nGA1SF_Ws#r#{NQyn0RC-3nQdz2x zwO9bz$RjQ#2s$3+s{<;0SjM4HdV(boYK;e)gRu~uZdtn#mZ3;2kZui!^jVw|Yjs$s z1j0ZGq1g?st-4luTt^-0EcV1Q|Q#qs7Gl^kNT7ppbia{K*xI%)45ui>*alt zhJ_EAk^d@;?B{r2cZ3>;MB6^fvOVvs3}Dn`uAldZOnE;D6`6E#%!*CO(cX*gdB4Tf z*?!hzx&!vKmt&syb4-1@CTIIKENHz2XUU#y zobhW1Mpf{B8jAsyH06D}MlTpYUx&bM@6mQl^K7W8qj<63Vc)A2nAY%%1&2Ks`-H=u z-#;*Qm|xKTC$&AtUwCxMnm(!HuTe|P{By{V3fuE~m}!U=HRXO}_%Jed`{8Spgy|j< z+7kC4L$uDpzYwkQ*EUB;1wIAYJ|BlC-t{(MEOE88=5p?s=>>*x1T1#O?OELDc>m|pC#-|16&J8KpFiXzt4 zW93w)2KH1I*Pr(({HV5PKNTv3hclW^)%9z2OUm}n`2*@Q*(u*UmN7CtT%ewq=(XY; q{Mc`v|LA=Sw;fq4_0WR}soo*vdX<#c{G;1XzeQ +#include + +static void clear(int b[20][10]) { + memset(b, 0, 20 * 10 * sizeof(int)); +} + +static void set(int b[20][10], int r, int c, int v) { + if (r >= 0 && r < 20 && c >= 0 && c < 10) b[r][c] = v; +} + +static void rect(int b[20][10], int r, int c, int h, int w, int v) { + for (int dr = 0; dr < h; dr++) + for (int dc = 0; dc < w; dc++) + set(b, r + dr, c + dc, v); +} + +/* tetromino shapes: flat arrays of 8 ints (4 cells * 2 coords) */ +static const int PIECE_T[] = {0,1, 1,0, 1,1, 1,2}; +static const int PIECE_L[] = {0,0, 0,1, 0,2, 1,0}; +static const int PIECE_J[] = {0,0, 0,1, 0,2, 1,2}; +static const int PIECE_S[] = {0,1, 0,2, 1,0, 1,1}; +static const int PIECE_Z[] = {0,0, 0,1, 1,1, 1,2}; +static const int PIECE_O[] = {0,0, 0,1, 1,0, 1,1}; +static const int PIECE_I[] = {0,0, 1,0, 2,0, 3,0}; + +static const int *PIECES[7] = {PIECE_T, PIECE_L, PIECE_J, PIECE_S, PIECE_Z, PIECE_O, PIECE_I}; +static const int PCOLORS[7] = {5, 3, 2, 1, 7, 4, 6}; + +static void place_piece(int b[20][10], int r, int c, int pi, int v) { + const int *cells = PIECES[pi % 7]; + for (int i = 0; i < 4; i++) + set(b, r + cells[i*2], c + cells[i*2+1], v); +} + +static void fill_next(int n[4][4], int pi) { + memset(n, 0, 4 * 4 * sizeof(int)); + const int *cells = PIECES[pi % 7]; + for (int i = 0; i < 4; i++) + n[cells[i*2]][cells[i*2+1]] = PCOLORS[pi % 7]; +} + +int main() { + gpu_t *gpu = gpu_init(); + if (!gpu) { fprintf(stderr, "failed to launch gpu\n"); return 1; } + + int b1[20][10], b2[20][10], n1[4][4], n2[4][4]; + clear(b1); + clear(b2); + + /* --- Player 1: stack with pieces on top --- */ + /* bottom layer */ + rect(b1, 16, 0, 4, 10, 1); + rect(b1, 15, 1, 1, 8, 2); + rect(b1, 14, 2, 1, 6, 3); + rect(b1, 13, 3, 1, 4, 4); + rect(b1, 12, 4, 1, 2, 5); + + /* a Tetromino falling */ + place_piece(b1, 2, 3, 0, PCOLORS[0]); /* T piece */ + + /* --- Player 2: different palette, similar stack --- */ + rect(b2, 16, 0, 4, 10, 1); + rect(b2, 15, 2, 1, 6, 2); + rect(b2, 14, 3, 1, 4, 3); + rect(b2, 13, 4, 1, 2, 4); + rect(b2, 12, 5, 1, 1, 5); + + place_piece(b2, 3, 4, 3, PCOLORS[3]); /* S piece */ + + /* --- Next pieces --- */ + fill_next(n1, 2); /* J */ + fill_next(n2, 5); /* O */ + + gpu_update(gpu, b1, b2, n1, n2); + + /* Animate: cycle through pieces falling on player 1 */ + for (int frame = 0; frame < 60; frame++) { + usleep(100000); /* 100ms */ + + clear(b1); + rect(b1, 16, 0, 4, 10, 1); + rect(b1, 15, 1, 1, 8, 2); + rect(b1, 14, 2, 1, 6, 3); + rect(b1, 13, 3, 1, 4, 4); + rect(b1, 12, 4, 1, 2, 5); + + int pi = frame / 8 % 7; + int row = 2 + (frame % 8); + place_piece(b1, row, 3, pi, PCOLORS[pi]); + fill_next(n1, (pi + 1) % 7); + + gpu_update(gpu, b1, b2, n1, n2); + } + + gpu_close(gpu); + return 0; +} diff --git a/tetris_gui/gpu.h b/tetris_gui/gpu.h new file mode 100644 index 0000000..5ed10bb --- /dev/null +++ b/tetris_gui/gpu.h @@ -0,0 +1,91 @@ +#ifndef GPU_H +#define GPU_H + +#include +#include + +#define GPU_BOARD_H 20 +#define GPU_BOARD_W 10 +#define GPU_NEXT_H 4 +#define GPU_NEXT_W 4 + +#ifndef GPU_SCRIPT +#define GPU_SCRIPT "/home/honney/Projects/tetris_gui/gpu.py" +#endif + +typedef struct { + FILE *pipe; +} gpu_t; + +static gpu_t *gpu_init(void) { + gpu_t *g = (gpu_t *)malloc(sizeof(gpu_t)); + if (!g) return NULL; + g->pipe = popen(GPU_SCRIPT, "w"); + if (!g->pipe) { free(g); return NULL; } + return g; +} + +static void gpu_update(gpu_t *g, + int b1[GPU_BOARD_H][GPU_BOARD_W], + int b2[GPU_BOARD_H][GPU_BOARD_W], + int n1[GPU_NEXT_H][GPU_NEXT_W], + int n2[GPU_NEXT_H][GPU_NEXT_W]) { + + FILE *p = g->pipe; + + fputs("{\"b1\":[", p); + for (int r = 0; r < GPU_BOARD_H; r++) { + if (r) fputc(',', p); + fputc('[', p); + for (int c = 0; c < GPU_BOARD_W; c++) { + if (c) fputc(',', p); + fprintf(p, "%d", b1[r][c]); + } + fputc(']', p); + } + + fputs("],\"b2\":[", p); + for (int r = 0; r < GPU_BOARD_H; r++) { + if (r) fputc(',', p); + fputc('[', p); + for (int c = 0; c < GPU_BOARD_W; c++) { + if (c) fputc(',', p); + fprintf(p, "%d", b2[r][c]); + } + fputc(']', p); + } + + fputs("],\"n1\":[", p); + for (int r = 0; r < GPU_NEXT_H; r++) { + if (r) fputc(',', p); + fputc('[', p); + for (int c = 0; c < GPU_NEXT_W; c++) { + if (c) fputc(',', p); + fprintf(p, "%d", n1[r][c]); + } + fputc(']', p); + } + + fputs("],\"n2\":[", p); + for (int r = 0; r < GPU_NEXT_H; r++) { + if (r) fputc(',', p); + fputc('[', p); + for (int c = 0; c < GPU_NEXT_W; c++) { + if (c) fputc(',', p); + fprintf(p, "%d", n2[r][c]); + } + fputc(']', p); + } + + fputs("]}\n", p); + fflush(p); +} + +static void gpu_close(gpu_t *g) { + if (g) { + if (g->pipe) fclose(g->pipe); + free(g); + } +} + +#endif diff --git a/tetris_gui/gpu.py b/tetris_gui/gpu.py new file mode 100755 index 0000000..700e98f --- /dev/null +++ b/tetris_gui/gpu.py @@ -0,0 +1,182 @@ +#!/home/honney/Projects/tetris_gui/.venv/bin/python3 +import sys, json, select, os +import pygame + +WIDTH, HEIGHT = 1280, 720 +CELL = 32 +BOARD_H, BOARD_W = 20, 10 +NEXT_H, NEXT_W = 4, 4 + +B1 = (304, 40, 320, 640) +B2 = (656, 40, 320, 640) +N1 = (88, 96, 128, 128) +N2 = (1064, 96, 128, 128) +S1 = (84, 454, 136, 32) +S2 = (1060, 454, 136, 32) + +SCORE_FONT = [ + [ # S + [0,1,1,1,1,0],[1,1,0,0,1,1],[1,1,0,0,0,0], + [0,1,1,1,0,0],[0,0,1,1,1,0],[0,0,0,0,1,1], + [1,1,0,0,1,1],[0,1,1,1,1,0], + ], + [ # C + [0,1,1,1,1,0],[1,1,1,0,1,1],[1,1,0,0,0,0], + [1,1,0,0,0,0],[1,1,0,0,0,0],[1,1,0,0,0,0], + [1,1,1,0,1,1],[0,1,1,1,1,0], + ], + [ # O + [0,1,1,1,1,0],[1,1,0,0,1,1],[1,1,0,0,1,1], + [1,1,0,0,1,1],[1,1,0,0,1,1],[1,1,0,0,1,1], + [1,1,0,0,1,1],[0,1,1,1,1,0], + ], + [ # R + [1,1,1,1,1,0],[1,1,0,0,1,1],[1,1,0,0,1,1], + [1,1,1,1,1,0],[1,1,0,1,1,1],[1,1,0,0,1,1], + [1,1,0,0,1,1],[1,1,0,0,1,1], + ], + [ # E + [1,1,1,1,1,1],[1,1,0,0,0,0],[1,1,0,0,0,0], + [1,1,1,1,1,1],[1,1,0,0,0,0],[1,1,0,0,0,0], + [1,1,0,0,0,0],[1,1,1,1,1,1], + ], +] + +PALETTE = [ + (0,0,0),(0,255,255),(0,0,255),(255,128,0), + (255,242,0),(0,255,0),(159,0,255),(255,0,0), +] +PALETTE_ALT = [ + (0,0,0),(0,134,243),(61,79,220),(253,171,1), + (171,203,4),(2,189,205),(210,15,99),(254,75,15), +] + +def bevel_color(r, g, b, x, y, style): + if style == 0: + if 8 <= x < 24 and 8 <= y < 24: + return (r, g, b) + if 6 <= x < 26 and 6 <= y < 26: + return (r & 0xB0, g & 0xB0, b & 0xB0) + if x + y < 31: + return (r, g, b) if x > y else (r & 0xEF, g & 0xEF, b & 0xEF) + return (r & 0xD7, g & 0xD7, b & 0xD7) if x > y else (r & 0xC0, g & 0xC0, b & 0xC0) + if x == 31 or y == 31: + return (r >> 1, g >> 1, b >> 1) if (x == 0 or y == 0) else (r >> 2, g >> 2, b >> 2) + if x == 0 or y == 0: + return (r, g, b) + return (r & 0xC0, g & 0xC0, b & 0xC0) + +def build_blocks(palette, style): + tex = [] + for clr in palette: + surf = pygame.Surface((CELL, CELL)) + for y in range(CELL): + for x in range(CELL): + surf.set_at((x, y), bevel_color(*clr, x, y, style)) + tex.append(surf) + return tex + +def build_background(): + surf = pygame.Surface((WIDTH, HEIGHT)) + for y in range(HEIGHT): + for x in range(WIDTH): + if (x + y) & 1: + surf.set_at((x, y), (0, 0, 255)) + else: + v = (x + y) >> 3 + surf.set_at((x, y), (v, 250 - v, 0)) + return surf + +def build_score(): + surf = pygame.Surface((136, 32)) + surf.fill((0, 0, 0)) + x_off = 0 + for glyph in SCORE_FONT: + for fy, row in enumerate(glyph): + for fx, px in enumerate(row): + if px: + pygame.draw.rect(surf, (255, 255, 255), (x_off + fx * 4, fy * 4, 4, 4)) + x_off += 28 + return surf + +class Display: + def __init__(self): + pygame.init() + self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) + pygame.display.set_caption("Tetris") + self.bg = build_background() + self.score = build_score() + self.b1_tex = build_blocks(PALETTE, 0) + self.b2_tex = build_blocks(PALETTE_ALT, 0) + self.n1_tex = build_blocks(PALETTE, 1) + self.n2_tex = build_blocks(PALETTE_ALT, 1) + self.board1 = [[0] * BOARD_W for _ in range(BOARD_H)] + self.board2 = [[0] * BOARD_W for _ in range(BOARD_H)] + self.next1 = [[0] * NEXT_W for _ in range(NEXT_H)] + self.next2 = [[0] * NEXT_W for _ in range(NEXT_H)] + + def draw_grid(self, board, rect, textures): + x, y, w, h = rect + for row in range(h // CELL): + for col in range(w // CELL): + v = board[row][col] + self.screen.blit(textures[v if 0 <= v <= 7 else 0], (x + col * CELL, y + row * CELL)) + + def render(self): + self.screen.blit(self.bg, (0, 0)) + self.draw_grid(self.board1, B1, self.b1_tex) + self.draw_grid(self.board2, B2, self.b2_tex) + self.draw_grid(self.next1, N1, self.n1_tex) + self.draw_grid(self.next2, N2, self.n2_tex) + self.screen.blit(self.score, (S1[0], S1[1])) + self.screen.blit(self.score, (S2[0], S2[1])) + pygame.display.flip() + + def close(self): + pygame.quit() + +def main(): + disp = Display() + buf = "" + clock = pygame.time.Clock() + running = True + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + if not sys.stdin.isatty(): + r, _, _ = select.select([sys.stdin], [], [], 0) + if r: + chunk = os.read(sys.stdin.fileno(), 65536) + if not chunk: + running = False + else: + buf += chunk.decode("utf-8") + while "\n" in buf: + line, buf = buf.split("\n", 1) + line = line.strip() + if not line: + continue + try: + data = json.loads(line) + if "b1" in data: + disp.board1 = data["b1"] + if "b2" in data: + disp.board2 = data["b2"] + if "n1" in data: + disp.next1 = data["n1"] + if "n2" in data: + disp.next2 = data["n2"] + except json.JSONDecodeError: + pass + + disp.render() + clock.tick(60) + + disp.close() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/tetris_gui/install.sh b/tetris_gui/install.sh new file mode 100755 index 0000000..6696079 --- /dev/null +++ b/tetris_gui/install.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +VENV_DIR="$SCRIPT_DIR/.venv" + +if [ -d "$VENV_DIR" ]; then + echo "Removing existing venv ..." + rm -rf "$VENV_DIR" +fi + +echo "Creating venv at $VENV_DIR ..." +python3 -m venv "$VENV_DIR" --without-pip + +echo "Bootstrapping pip ..." +curl -sL https://bootstrap.pypa.io/get-pip.py | "$VENV_DIR/bin/python3" + +echo "Installing dependencies ..." +"$VENV_DIR/bin/pip" install -r "$SCRIPT_DIR/requirements.txt" + +echo "Fixing shebang in gpu.py ..." +if [ -f "$SCRIPT_DIR/gpu.py" ]; then + sed -i "1s|^#!.*|#!$VENV_DIR/bin/python3|" "$SCRIPT_DIR/gpu.py" +fi + +echo "Done. Activate with: source $VENV_DIR/bin/activate" diff --git a/tetris_gui/requirements.txt b/tetris_gui/requirements.txt new file mode 100644 index 0000000..183ccce --- /dev/null +++ b/tetris_gui/requirements.txt @@ -0,0 +1 @@ +pygame>=2.5.0