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 0000000..15c0c16 Binary files /dev/null and b/tetris_gui/demo differ diff --git a/tetris_gui/demo.c b/tetris_gui/demo.c new file mode 100644 index 0000000..b16ad0c --- /dev/null +++ b/tetris_gui/demo.c @@ -0,0 +1,99 @@ +#include "gpu.h" +#include +#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