commit 15d842b82dc0552a512c200a447f03ec7736f408 Author: T zkdream Date: Sat Apr 6 21:02:47 2024 +0300 Push diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b7fb3a1 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +ARCH = $(shell uname -m) +# no-builtin: do not generate memcpy() calls +# no-stack-protector: do not attempt to load SSP canary from TLS +# no-unwind-tables: avoid eh_frame sections on AArch64 +# no-asynchronous-unwind-tables: avoid eh_frame sections +CFLAGS = -Os -march=native -std=c11 -pipe -Wall -Wconversion \ + -fno-builtin \ + -fno-jump-tables \ + -fno-stack-protector \ + -ffunction-sections \ + -fno-unwind-tables \ + -fno-asynchronous-unwind-tables +CPPFLAGS = -Iinclude +LD ?= ld.gold +LDFLAGS = -s -N -O3 --gc-sections + +ifdef DEBUG +CFLAGS += -O0 -g -fasynchronous-unwind-tables +LDFLAGS = +endif + +ifeq ($(ARCH), i686) + CPPFLAGS += -DARCH_386 +else ifeq ($(ARCH), aarch64) + CPPFLAGS += -DARCH_aarch64 +else + CFLAGS += -m64 + LDFLAGS += -melf_x86_64 + CPPFLAGS += -DARCH_amd64 + ARCH = amd64 +endif + +LIB_OBJS = lib/print.o lib/time.o lib/int64.o lib/random.o lib/stub.o +LIB_OBJS += lib/$(ARCH)/skel.o lib/$(ARCH)/sys.o lib/$(ARCH)/sys_time.o + +all: main.bin mandelbrot.bin random.bin randomline.bin tinytris.bin + +%.bin: %.o stdlib.a + $(LD) $(LDFLAGS) -o $@ $< stdlib.a + +%.o: %.s + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +stdlib.a: $(LIB_OBJS) + ar cru stdlib.a $(LIB_OBJS) + +format: + clang-format -i *.c include/*.h lib/*.c + +clean: + rm -f *.bin *.o lib/*.o lib/*/*.o stdlib.a diff --git a/include/print.h b/include/print.h new file mode 100644 index 0000000..3497b8f --- /dev/null +++ b/include/print.h @@ -0,0 +1,19 @@ +#ifndef PRINT_H +#define PRINT_H + +#include + +#define print(x) \ + _Generic((x), int16_t \ + : printint, int32_t \ + : printint, int64_t \ + : printint, uint16_t \ + : printuint, uint32_t \ + : printuint, uint64_t \ + : printuint, char*: printstr)(x) + +void printint(int64_t n); +void printuint(uint64_t n); +void printstr(const char *s); + +#endif diff --git a/include/random.h b/include/random.h new file mode 100644 index 0000000..1daa0e5 --- /dev/null +++ b/include/random.h @@ -0,0 +1,13 @@ +#ifndef RANDOM_H +#define RANDOM_H + +#include + +typedef struct { + int64_t seed; +} RandGen; + +RandGen randinit(); +int64_t randnext(RandGen *g); + +#endif diff --git a/include/sys.h b/include/sys.h new file mode 100644 index 0000000..f658e52 --- /dev/null +++ b/include/sys.h @@ -0,0 +1,23 @@ +#ifndef SYS_H +#define SYS_H + +#include + +int read(int fd, const char *buffer, int size); +int write(int fd, const char *buffer, int size); +int ioctl(int fd, int cmd, void *args); +_Noreturn void exit(int code); + +int select(int n, void *r, void *w, void *e, struct UTime *tv); + +void nanosleep(struct NTime *t, struct NTime *rem); +void usleep(unsigned long usecs); + +int gettimeofday(struct UTime *t, struct Timezone *tz); +int64_t utime(); + +// ioctl constants +#define TCGETS 0x5401 +#define TCSETSF 0x5404 + +#endif diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..ecb8eb5 --- /dev/null +++ b/include/types.h @@ -0,0 +1,22 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +struct UTime { + size_t secs; + size_t usecs; +}; + +struct NTime { + size_t secs; + size_t nsecs; +}; + +struct Timezone { + int tz_minuteswest; + int tz_dsttime; +}; + +#endif diff --git a/lib/aarch64/skel.s b/lib/aarch64/skel.s new file mode 100644 index 0000000..88c9d3a --- /dev/null +++ b/lib/aarch64/skel.s @@ -0,0 +1,7 @@ +.text +.globl _start + +_start: + bl main // returns to x0 + mov x8, 94 // SYS_exit_group + svc 0 diff --git a/lib/aarch64/sys.s b/lib/aarch64/sys.s new file mode 100644 index 0000000..302bf2c --- /dev/null +++ b/lib/aarch64/sys.s @@ -0,0 +1,29 @@ +.section .text.read +.globl read +read: + mov x8, 0x3f // SYS_read + svc 0 + ret + +// write(int fd=r0, char *data=r1, int data_len=r2) +.section .text.write +.globl write +write: + mov x8, 0x40 // SYS_write + svc 0 + ret + +.section .text.ioctl +.globl ioctl +ioctl: + mov x8, 29 // SYS_ioctl + svc 0 + ret + +.section .text.exit +.globl exit +exit: + mov x8, 94 // SYS_exit_group + svc 0 + ret + diff --git a/lib/aarch64/sys_time.s b/lib/aarch64/sys_time.s new file mode 100644 index 0000000..968eef7 --- /dev/null +++ b/lib/aarch64/sys_time.s @@ -0,0 +1,24 @@ +// pselect6(int n, void *read_fds, void *write_fds, void *err_fds, void *tv, void *sigmask) +.section .text.pselect6 +.globl pselect6 +pselect6: + mov x8, 0x48 // SYS_pselect6 + svc 0 + ret + +// nanosleep(timespec *t, timespec *rem) +.section .text.nanosleep +.globl nanosleep +nanosleep: + mov x8, 0x65 // SYS_nanosleep + svc 0 + ret + +// gettimeofday(timeval *t, timezone *tz) +.section .text.gettimeofday +.globl gettimeofday +gettimeofday: + mov x8, 0xa9 + svc 0 + ret + diff --git a/lib/amd64/memset.s b/lib/amd64/memset.s new file mode 100644 index 0000000..a2433a5 --- /dev/null +++ b/lib/amd64/memset.s @@ -0,0 +1,8 @@ +// fill(long *dest, long value, long length) +.global fill +fill: + mov %rsi, %rax + mov %rdx, %rcx + rep stosq + ret + diff --git a/lib/amd64/skel.o b/lib/amd64/skel.o new file mode 100644 index 0000000..e21430c Binary files /dev/null and b/lib/amd64/skel.o differ diff --git a/lib/amd64/skel.s b/lib/amd64/skel.s new file mode 100644 index 0000000..bdbeff7 --- /dev/null +++ b/lib/amd64/skel.s @@ -0,0 +1,8 @@ +.text +.globl _start + +_start: + call main + mov %rax, %rdi + call exit + diff --git a/lib/amd64/sys.o b/lib/amd64/sys.o new file mode 100644 index 0000000..89e8db8 Binary files /dev/null and b/lib/amd64/sys.o differ diff --git a/lib/amd64/sys.s b/lib/amd64/sys.s new file mode 100644 index 0000000..4753362 --- /dev/null +++ b/lib/amd64/sys.s @@ -0,0 +1,35 @@ +.section .text.read + +.globl read +read: + // fd in %edi, data in %rsi, data_len in %edx + movl $0, %eax + syscall + ret + +.section .text.write + +// write(int fd, char *data, int data_len) +.globl write +write: + // fd in %edi, data in %rsi, data_len in %edx + movl $1, %eax + syscall + ret + +.section .text.ioctl + +.globl ioctl +ioctl: + movl $16, %eax + syscall + ret + +.section .text.exit + +// exit(int code) +.globl exit +exit: + mov $231, %eax + syscall + diff --git a/lib/amd64/sys_time.o b/lib/amd64/sys_time.o new file mode 100644 index 0000000..543dbee Binary files /dev/null and b/lib/amd64/sys_time.o differ diff --git a/lib/amd64/sys_time.s b/lib/amd64/sys_time.s new file mode 100644 index 0000000..c6fd8dd --- /dev/null +++ b/lib/amd64/sys_time.s @@ -0,0 +1,33 @@ +.section .text.select + +// select +// int n %edi +// void *read_fds %rsi +// void *write_fds %rdx +// void *err_fds %rcx +// void *tv %r8 +.globl select +select: + movl $23, %eax + movq %rcx, %r10 + syscall + ret + +.section .text.nanosleep + +// nanosleep(timespec *t, timespec *rem) +.globl nanosleep +nanosleep: + movl $35, %eax + syscall + ret + +.section .text.gettimeofday + +// gettimeofday(timeval *t, timezone *tz) +.globl gettimeofday +gettimeofday: + movl $96, %eax + syscall + ret + diff --git a/lib/i386/skel.s b/lib/i386/skel.s new file mode 100644 index 0000000..a82cd23 --- /dev/null +++ b/lib/i386/skel.s @@ -0,0 +1,9 @@ +.text +.globl _start + +_start: + call main + movl %eax, %ebx + movl $1, %eax + int $0x80 + diff --git a/lib/i386/sys.s b/lib/i386/sys.s new file mode 100644 index 0000000..38de296 --- /dev/null +++ b/lib/i386/sys.s @@ -0,0 +1,39 @@ +.text + +.globl read +read: + movl $3, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + movl 12(%esp), %edx + int $0x80 + ret + +// write(int fd, char *data, int data_len) +.globl write +write: + movl $4, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + movl 12(%esp), %edx + int $0x80 + ret + +// ioctl(int fd, int cmd, void *args); +.globl ioctl +ioctl: + movl $0x36, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + movl 12(%esp), %edx + int $0x80 + ret + +// exit(int code) +.globl exit +exit: + mov $0xfc, %eax + movl 4(%esp), %ebx + int $0x80 + ret + diff --git a/lib/i386/sys_time.s b/lib/i386/sys_time.s new file mode 100644 index 0000000..d0ba9bf --- /dev/null +++ b/lib/i386/sys_time.s @@ -0,0 +1,32 @@ +.text + +// select(int n, void *read_fds, void *write_fds, void *err_fds, void *tv) +.globl select +select: + movl $142, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + movl 12(%esp), %edx + movl 16(%esp), %esi + movl 20(%esp), %edi + int $0x80 + ret + +// nanosleep(timespec *t, timespec *rem) +.globl nanosleep +nanosleep: + movl $162, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + int $0x80 + ret + +// gettimeofday(timeval *t, timezone *tz) +.globl gettimeofday +gettimeofday: + movl $78, %eax + movl 4(%esp), %ebx + movl 8(%esp), %ecx + int $0x80 + ret + diff --git a/lib/int64.c b/lib/int64.c new file mode 100644 index 0000000..b049293 --- /dev/null +++ b/lib/int64.c @@ -0,0 +1,64 @@ +#if ARCH_386 + +#include +#include + +typedef size_t uint; + +static void quotrem64(a, b, q, r) uint64_t a, b; +uint64_t *q, *r; +{ + double qfloat; + + // Special case + if (a < b) { + *q = 0; + if (r != NULL) + *r = a; + } + + // Use float for approximation + qfloat = (double)a / (double)b; + if (b >> 16) { + // Quotient has less than 48 bits + *q = (uint64_t)qfloat; + if (r != NULL) + *r = a - b * (*q); + } else { + uint64_t qapprox, rem; + qapprox = (uint64_t)qfloat; + rem = a - b * qapprox; + *q = qapprox + (uint64_t)((uint)rem / (uint)b); + if (r != NULL) + *r = (uint)rem % (uint)b; + } +} + +#define ONE_FOURTH (1 << 30) +#define ONE_HALF (ONE_FOURTH * 2.0) +#define ONE (ONE_FOURTH * 4.0) + +uint64_t __fixunsdfdi(double x) { + double upper = x / ONE; + uint64_t result = (uint)upper; + result <<= 32; + while (result > x) + result -= (1ULL << 32); + x -= (double)result; + result += (uint)x; + return result; +} + +uint64_t __divdi3(uint64_t a, uint64_t b) { + uint64_t q; + quotrem64(a, b, &q, NULL); + return q; +} + +uint64_t __moddi3(uint64_t a, uint64_t b) { + uint64_t q, r; + quotrem64(a, b, &q, &r); + return r; +} + +#endif diff --git a/lib/int64.o b/lib/int64.o new file mode 100644 index 0000000..ab1074a Binary files /dev/null and b/lib/int64.o differ diff --git a/lib/print.c b/lib/print.c new file mode 100644 index 0000000..f9d6a85 --- /dev/null +++ b/lib/print.c @@ -0,0 +1,54 @@ +#include +#include + +void printuint(uint64_t n) { + char digits[20]; + int x = 19; + uint64_t hi = 0; + const uint64_t N_1E9 = 1000000000; + while (n) { + if (n >= N_1E9) { + hi = n / N_1E9; + uint32_t lo = (uint32_t)(n % N_1E9); + for (int i = 0; i < 9; i++) { + uint32_t digit = lo % 10; + lo = lo / 10; + digits[x] = '0' + (char)digit; + x--; + } + n = hi; + } else { + uint32_t lo = (uint32_t)n; + uint32_t digit = lo % 10; + lo = lo / 10; + digits[x] = '0' + (char)digit; + x--; + n = lo; + } + } + if (x == 19) { + digits[x] = '0'; + x--; + } + write(1, &digits[x + 1], 19 - x); +} + +void printint(int64_t n) { + if (n < 0) { + char minus = '-'; + write(1, &minus, 1); + printuint((uint64_t)(-n)); + } else { + printuint((uint64_t)(n)); + } +} + +void printstr(const char *s) { + char *p = s; + int l = 0; + while (*p) { + l++; + p++; + } + write(1, s, l); +} diff --git a/lib/print.o b/lib/print.o new file mode 100644 index 0000000..47e369b Binary files /dev/null and b/lib/print.o differ diff --git a/lib/random.c b/lib/random.c new file mode 100644 index 0000000..0495be7 --- /dev/null +++ b/lib/random.c @@ -0,0 +1,20 @@ +#include +#include + +RandGen randinit(); +int64_t randnext(RandGen *g); + +RandGen randinit() { + RandGen g; + g.seed = utime(); + return g; +} + +int64_t randnext(RandGen *g) { + int64_t ret = g->seed; + ret = 0x5DEECE66D * ret + 0xB; + ret &= 0xffffffffffff; + g->seed = ret; + ret = ret ^ (ret >> 16) ^ (ret >> 32); + return ret; +} diff --git a/lib/random.o b/lib/random.o new file mode 100644 index 0000000..c9c0bd3 Binary files /dev/null and b/lib/random.o differ diff --git a/lib/stub.c b/lib/stub.c new file mode 100644 index 0000000..d8f9723 --- /dev/null +++ b/lib/stub.c @@ -0,0 +1,3 @@ +#include + +void __stack_chk_fail() { write(2, "stack overflow\n", 15); } diff --git a/lib/stub.o b/lib/stub.o new file mode 100644 index 0000000..eff2745 Binary files /dev/null and b/lib/stub.o differ diff --git a/lib/time.c b/lib/time.c new file mode 100644 index 0000000..de5142a --- /dev/null +++ b/lib/time.c @@ -0,0 +1,26 @@ +#include + +void usleep(unsigned long usecs) { + struct NTime t; + t.secs = usecs / 1000000; + t.nsecs = 1000 * (usecs % 1000000); + nanosleep(&t, NULL); +} + +int64_t utime() { + struct UTime t; + gettimeofday(&t, NULL); + return t.secs * 1000000 + t.usecs; +} + +#if ARCH_aarch64 +// emulate select with pselect6 + +int pselect6(int nfds, void *r, void *w, void *e, struct NTime *t, void *p); + +int select(int nfds, void *r, void *w, void *e, struct UTime *t) { + struct NTime nt = {t->secs, 1000 * t->usecs}; + int ret = pselect6(nfds, r, w, e, &nt, NULL); + return ret; +} +#endif diff --git a/lib/time.o b/lib/time.o new file mode 100644 index 0000000..758ee47 Binary files /dev/null and b/lib/time.o differ diff --git a/main.bin b/main.bin new file mode 100755 index 0000000..8f085fc Binary files /dev/null and b/main.bin differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..8f8d948 --- /dev/null +++ b/main.c @@ -0,0 +1,6 @@ +#include "sys.h" + +int main() { + write(1, "Hello world!\n", 13); + return 17; +} diff --git a/mandelbrot.bin b/mandelbrot.bin new file mode 100755 index 0000000..6f69319 Binary files /dev/null and b/mandelbrot.bin differ diff --git a/mandelbrot.c b/mandelbrot.c new file mode 100644 index 0000000..319bc37 --- /dev/null +++ b/mandelbrot.c @@ -0,0 +1,76 @@ +#include "sys.h" + +#define ROWS 25 +#define COLS 80 +#define ASPECT 1.6 +#define VERBATIM(s) #s +#define AS_STRING(s) VERBATIM(s) + +const char *header = "\r\e[" AS_STRING(ROWS) "A"; + +static int test(c1, c2) +double c1, c2; +{ + double x, y; + x = y = 0; + for (int n = 0; n < 1000; n++) { + double newx = x * x - y * y + c1; + double newy = 2 * x * y + c2; + x = newx; + y = newy; + if ((x * x + y * y) > 10.0) { + return n / 124; + } + } + return 9; +} + +// double refx = 0.3245046418497685; +// double refy = 0.04855101129280834; + +// double refx = 0.3215000630401344; +// double refy = 0.04855009999999; + +// double refx = 0.32158; +// double refy = 0.048; + +double refx = 0.32151002; +double refy = 0.04800001; + +char buffer[32768]; + +int main() { + int pos = 0; + double scale = 2; + double x, y; + for (int i = 0; i < 610; i++) { + y = refy + scale; + for (int i = 0; i < ROWS; i++) { + x = refx - scale * ASPECT; + y -= 2 * scale / (double)(ROWS); + for (int j = 0; j < COLS; j++) { + x += 2 * scale / (double)(COLS)*ASPECT; + int r = test(x, y); + buffer[pos++] = '\e'; + buffer[pos++] = '['; + buffer[pos++] = '4'; + buffer[pos++] = r + '0'; + buffer[pos++] = 'm'; + buffer[pos++] = ' '; + } + buffer[pos++] = '\e'; + buffer[pos++] = '['; + buffer[pos++] = '0'; + buffer[pos++] = 'm'; + buffer[pos++] = '\n'; + } + write(1, buffer, pos); + usleep(30000); + scale *= 0.95; + // Rewind cursor + pos = 0; + for (const char *p = header; *p; p++) + buffer[pos++] = *p; + } + return 0; +} diff --git a/random.bin b/random.bin new file mode 100755 index 0000000..54dfd53 Binary files /dev/null and b/random.bin differ diff --git a/random.c b/random.c new file mode 100644 index 0000000..7bbde6d --- /dev/null +++ b/random.c @@ -0,0 +1,19 @@ +#include +#include +#include + +int main() { + RandGen g = randinit(); + int i; + int64_t n; + int buckets[19]; + + for (i = 0; i < 3000000; i++) { + n = randnext(&g); + buckets[n % 19] += 1; + } + for (i = 0; i < 19; i++) { + print(buckets[i]); + print("\n"); + } +} diff --git a/randomline.bin b/randomline.bin new file mode 100755 index 0000000..5bdfb21 Binary files /dev/null and b/randomline.bin differ diff --git a/randomline.c b/randomline.c new file mode 100644 index 0000000..9223092 --- /dev/null +++ b/randomline.c @@ -0,0 +1,60 @@ +#include +#include +#include + +#define BUFSIZE 4096 + +static void copybytes(char *dest, const char *src, int length) { + register const char *p; + const char *end = src + length; + for (p = src; p < end; p++) + *dest++ = *p; +} + +// Copy a line including the terminating \n and returns the length. +static int readline(char *dest, int destsize) { + static char buffer[BUFSIZE]; + static int pos; + if (pos < BUFSIZE / 2) { + int n_read = read(0, buffer + pos, BUFSIZE - pos); + if ((n_read == 0) && (pos == 0)) { + write(2, "End of file.\n", 13); + return -1; + } + pos += n_read; + } + + for (const char *p = buffer; p < buffer + pos; p++) { + if (*p == '\n') { + p++; + int linelength = p - buffer; + copybytes(dest, buffer, linelength); + copybytes(buffer, p, BUFSIZE - linelength); + pos -= linelength; + return linelength; + } + } + write(2, "Buffer capacity exceeded\n", 25); + return -1; +} + +int main() { + char selected[4096]; + char buffer[4096]; + int lineno = 0; + int selsize = 0; + RandGen g = randinit(); + for (;;) { + int len = readline(buffer, 4096); + if (len <= 0) + break; + lineno++; + int64_t rnd = randnext(&g); + if (rnd % lineno == 0) { + copybytes(selected, buffer, len); + selsize = len; + } + } + write(1, selected, selsize); + return 0; +} diff --git a/stdlib.a b/stdlib.a new file mode 100644 index 0000000..99b0b3b Binary files /dev/null and b/stdlib.a differ diff --git a/tinytris.bin b/tinytris.bin new file mode 100755 index 0000000..bfcc612 Binary files /dev/null and b/tinytris.bin differ diff --git a/tinytris.c b/tinytris.c new file mode 100644 index 0000000..c266477 --- /dev/null +++ b/tinytris.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include + +#define FRAME_DURATION_USECS 200000 +#define WIDTH 24 + +// contains ' ' or '#' +char board[10 * 20]; +char screen[WIDTH * 21]; + +struct state { + uint16_t score; + int x, y; + uint32_t shape, angle; + uint16_t shpbits; + RandGen g; + + char lastkey; +} state; + +uint16_t getshape(uint32_t idx, uint32_t angle) { + int shift1 = 16 * (angle & 1); + int shift2 = 12 * (angle & 3); + + static const uint32_t shapes_sym[3] = { + 0x444400F0ul, + // ## # + // ## ## + // # + 0x02310036ul, + // ## # + // ## ## + // # + 0x01320063ul, + }; + + static const uint64_t shapes_asym[3] = { + // 270 262 072 232 + // ### # # # + // # ## ### ## + // # # + 0x270262072232ull, + // 170 622 074 223 + // ### ## # # + // # # ### # + // # ## + 0x170622074223ull, + // 470 226 071 322 + 0x470226071322ull, + }; + + switch (idx) { + case 1: + case 2: + case 3: + return (shapes_sym[idx - 1] >> shift1) & 0xffff; + case 4: + return 0x0660; + case 5: + case 6: + case 7: + return (shapes_asym[idx - 5] >> shift2) & 0xfff; + default: + exit(43); // impossible + } +} + +static bool fits(int px, int py) { + for (int bit = 0; bit < 16; bit++) { + int sx = px + bit % 4; + int sy = py + bit / 4; + if (((state.shpbits >> bit) & 1) == 0) + continue; + if (sx < 0 || sx >= 10) + return false; + if (sy < 0 || sy >= 20) + return false; + if (board[10 * sy + sx] != ' ') + return false; + } + return true; +} + +static inline void copy8(char *dst, const char *src) { + *(uint64_t *)(dst) = *(uint64_t *)(src); +} + +static inline void copy4(char *dst, const char *src) { + *(uint32_t *)(dst) = *(uint32_t *)(src); +} + +static inline void copy2(char *dst, const char *src) { + *(uint16_t *)(dst) = *(uint16_t *)(src); +} + +static void init_board() { + for (int i = 0; i < 10 * 20; i++) + board[i] = ' '; +} + +// Find complete lines and pop them. +static void poplines(struct state *st) { + for (int i = 0; i < 20; i++) { + bool win = true; + for (int j = 0; j < 10; j++) { + if (board[10 * i + j] != '#') + win = false; + } + if (win) { + st->score++; + // slide down + for (int k = i; k > 0; k--) { + copy8(&board[10 * k], &board[10 * (k - 1)]); + copy2(&board[10 * k + 8], &board[10 * (k - 1) + 8]); + } + // top line + for (int j = 0; j < 10; j++) + board[j] = ' '; + } + } +} + +static void init_screen() { + for (int i = 0; i <= 20; i++) { + for (int j = 0; j < WIDTH; j++) { + char c = ' '; + if (j == 10) + c = '|'; + if (i == 20 && j <= 10) + c = '-'; + if (j == WIDTH - 1) + c = '\n'; + screen[WIDTH * i + j] = c; + } + } + // help + const char s0[8] = " LINES "; + copy8(&screen[WIDTH * 7 + 13], s0); + const char s2[8] = "2 down "; + copy8(&screen[WIDTH * 11 + 13], s2); + const char s4[4] = "4 <-"; + copy4(&screen[WIDTH * 12 + 13], s4); + const char s5[8] = "5 rotate"; + copy8(&screen[WIDTH * 13 + 13], s5); + const char s6[4] = "6 ->"; + copy4(&screen[WIDTH * 14 + 13], s6); + const char sq[8] = "q quit "; + copy8(&screen[WIDTH * 15 + 13], sq); +} + +void update_screen(struct state *st) { + for (int i = 0; i < 20; i++) { + // copy 10 bytes + *(uint64_t *)(&screen[WIDTH * i]) = *(uint64_t *)(&board[10 * i]); + *(uint16_t *)(&screen[WIDTH * i + 8]) = *(uint16_t *)(&board[10 * i + 8]); + } + // draw shape + for (int bit = 0; bit < 16; bit++) { + int sx = st->x + bit % 4; + int sy = st->y + bit / 4; + if ((st->shpbits >> bit) & 1) + screen[WIDTH * sy + sx] = '@'; + } + // show score on line 8: + uint16_t score = st->score; + if (score >= 1000) + score = 999; + char score_str[4] = { + ' ', + score >= 100 ? '0' + (score / 100) : ' ', + score >= 10 ? '0' + ((score / 10) % 10) : ' ', + '0' + (score % 10), + }; + copy4(&screen[WIDTH * 8 + 14], score_str); +} + +static void rewind() { + const char esc[6] = "\r\e[21A"; + write(1, esc, 6); +} + +static void show_screen() { write(1, screen, WIDTH * 21); } + +static void newshape(struct state *st) { + while (1) { + uint64_t rnd = (uint64_t)randnext(&st->g); + uint32_t s = (rnd >> 16) % 8; + uint32_t s2 = (rnd >> 28) % 4; + if (s > 0) { + st->x = 3; + st->y = 0; + st->shape = s; + st->angle = s2; + st->shpbits = getshape(s, s2); + return; + } + } +} + +void next_frame(struct state *st) { + // Wait for next frame or next key. + struct UTime dt = {0, FRAME_DURATION_USECS}; + // Bitmask for stdin + uint64_t readmask = 1; + select(1, &readmask, NULL, NULL, &dt); + if (readmask & 1) { + char key; + read(0, &key, 1); + st->lastkey = key; + } else { + st->lastkey = ' '; + } + // handle controls + const int x = st->x; + const int y = st->y; + switch (st->lastkey) { + case '4': + if (fits(x - 1, y)) + st->x--; + return; + case '5': + st->angle++; + st->shpbits = getshape(st->shape, st->angle); + if (!fits(x, y)) { + st->angle--; + st->shpbits = getshape(st->shape, st->angle); + } + case '6': + if (fits(x + 1, y)) + st->x++; + return; + case 'q': + exit(0); + } + // move shape down + if (fits(x, y + 1)) + st->y++; + else { + // freeze shape + if (fits(x, y)) { + for (int bit = 0; bit < 16; bit++) { + int sx = st->x + bit % 4; + int sy = st->y + bit / 4; + if (((st->shpbits >> bit) & 1) == 0) + continue; + board[10 * sy + sx] = '#'; + } + } else { + // impossible + exit(42); + } + poplines(st); + newshape(st); + + if (!fits(st->x, st->y)) { + write(1, "GAME OVER\n", 10); + exit(1); + } + } +} + +static void setup_tty() { + // Deactivate echo and buffering. + struct termios st; + ioctl(0, TCGETS, &st); + st.c_lflag &= ~(uint32_t)(ICANON | ECHO); + ioctl(0, TCSETSF, &st); +} + +int main() { + state.lastkey = ' '; + state.score = 0; + setup_tty(); + init_board(); + init_screen(); + show_screen(); + newshape(&state); + while (1) { + update_screen(&state); + rewind(); + show_screen(); + next_frame(&state); + } + return 0; +}