Skip to content

Instantly share code, notes, and snippets.

@nomissbowling
Created January 11, 2019 04:06
Show Gist options
  • Save nomissbowling/9a5a6fc1468b067aaf438f49cf91096d to your computer and use it in GitHub Desktop.
Save nomissbowling/9a5a6fc1468b067aaf438f49cf91096d to your computer and use it in GitHub Desktop.
One Hour Tetris (sm8517855)
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import sys, os
import numpy as np
import time
import copy
import ctypes
import struct
import binascii
from PIL import Image
from io import BytesIO
PNG = '''
89504E47 0D0A1A0A
0000000D 49484452 00000140 000000F0 08 02 00 00 00 FE4F2A3C
00000003 73424954 080808 DBE14FE0
00000009 70485973 00000EC3 00000EC3 01 C76FA864
00000016 74455874 43726561 74696F6E 2054696D 65003038 2F32392F 3130 33E184AC
00000021 74455874 536F6674 77617265 004D6163 726F6D65
64696120 46697265 776F726B 7320342E 30 EA262775
000003BB 49444154
789CEDDD 216E5C57 1480E133 D183215D 434061AC 1A9A7505 A55179A4 D02E26DB
C8168C0A E25809A8 59D42594 1416BC80 14549D67 F00C9EE7 97BE0FDE 4B0EF965
DF7B3533 A7EB759D 2D9F3657 814BB2CC CCDC9F2D DFCFBC3D 7C1660A7 17CF3D00
F0740286 30014398 80214CC0 10F6C82D 345070FA 7BFD6973 E3A5ACE1 E22D33F3
F2EBD9F2 DDCC9BE3 8701F671 06863001 43988021 4CC01026 60085B66 66EE9E7B
0AE0494E EBD587ED 9DCFBF1C 3B09B0DB 3233331F CFD61F66 040C97CE 1918C204
0C610286 30014398 8021ECFB 2DF4C333 4F013CC9 E9DDFA7A 73E3FD7C 39781460
AF65667E FFF7EFF0 7FBD3A7E 14602F67 60081330 840918C2 040C6102 86B0EFF7
CFEE9C21 69B9B9FA 72B3B9E3 19182EDE 32337F9E ADDECEFC 7AF828C0 5ECEC010
26600813 30840918 C2040C61 CBCCDC3E F710C0D3 9CD64F57 DB3BD79F 8F9D04D8
6D9999F9 E36CF96E E6FAF059 809D9C81 214CC010 26600813 30840918 C2FC3E30
849DFEBA 5A37377E F00C0C17 6F99479E 817F3B7E 16602767 60081330 840918C2
040C6102 86B0653C 0343D6F2 F3F6B742 7B468280 6566E6E3 FF577FFC E7F84980
DD9C8121 4CC01026 60081330 840918C2 9671E70C 59A775DD FE3C3070 F9FC0B0D
61028630 01439880 214CC010 26600813 30840918 C2040C61 02863001 43988021
4CC01026 60081330 840918C2 040C6102 86300143 9880214C C0102660 08133084
0918C204 0C610286 30014398 80214CC0 10266008 13308409 18C2040C 61028630
01439880 214CC010 26600813 30840918 C2040C61 02863001 43988021 4CC01026
60081330 840918C2 040C6102 86300143 9880214C C0102660 08133084 0918C204
0C610286 30014398 80214CC0 10266008 13308409 18C2040C 61028630 01439880
214CC010 26600813 30840918 C2040C61 02863001 43988021 4CC01026 60081330
840918C2 040C6102 86300143 9880214C C0102660 08133084 0918C204 0C610286
30014398 80214CC0 10266008 13308409 18C2040C 61028630 01439880 214CC010
26600813 30840918 C2040C61 02863001 43988021 4CC01026 60081330 840918C2
040C6102 86300143 9880214C C0102660 08133084 0918C204 0C610286 30014398
80214CC0 10266008 13308409 18C2040C 61028630 01439880 214CC010 26600813
30840918 C2040C61 02863001 43988021 4CC01026 60081330 840918C2 040C6102
86300143 9880214C C0102660 08133084 0918C204 0C610286 30014398 80214CC0
10266008 13308409 18C2040C 61028630 01439880 214CC010 26600813 30840918
C2040C61 02863001 43988021 4CC01026 60081330 847D036D D92ED6 929559F3
00000000 49454E44 AE426082
'''
def loadpng(s):
b = binascii.a2b_hex(s.replace(' ', '').replace('\x0A', ''))
return Image.open(BytesIO(b)).convert('RGB')
from ctypes.wintypes import HANDLE, HINSTANCE, HICON, HBRUSH, POINT
from ctypes.wintypes import BOOL, HWND, UINT, WPARAM, LPARAM
from ctypes.wintypes import BYTE, WORD, DWORD, ULONG, LONG, INT
from ctypes.wintypes import LPCSTR, LPCWSTR
kernel32 = ctypes.windll.kernel32
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
CS_HREDRAW = 0x0002
CS_VREDRAW = 0x0001
IDC_ARROW = 0x7F00
COLOR_WINDOW = 5
IMAGE_ICON = 1
LR_DEFAULTCOLOR = 0x00000000
WS_OVERLAPPED = 0x00000000
WS_MINIMIZEBOX = 0x00020000
WS_SYSMENU = 0x00080000
WS_CAPTION = 0x00C00000
CW_USEDEFAULT = 0x80000000
WS_EX_WINDOWEDGE = 0x00000100
WS_EX_CLIENTEDGE = 0x00000200
WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE
SW_SHOW = 5
WM_CREATE = 0x0001
WM_DESTROY = 0x0002
WM_PAINT = 0x000F
WM_TIMER = 0x0113
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
DIB_RGB_COLORS = 0
SRCCOPY = 0x00CC0020
WNDPROC = ctypes.WINFUNCTYPE(BOOL, HWND, UINT, WPARAM, LPARAM) # LRESULT
DefWndProc = user32.DefWindowProcW
DefWndProc.restype = BOOL
DefWndProc.argtypes = (HWND, UINT, WPARAM, LPARAM)
class WNDCLASSEX(ctypes.Structure):
_fields_ = [('cbSize', UINT), ('style', UINT),
('lpfnWndProc', WNDPROC), ('cbClsExtra', INT), ('cbWndExtra', INT),
('hInstance', HINSTANCE), ('hIcon', HICON), ('hCursor', HANDLE), # HCURSOR
('hbrBackground', HBRUSH),
('lpszMenuName', LPCWSTR), ('lpszClassName', LPCWSTR), ('hIconSm', HICON)]
class MSG(ctypes.Structure):
_fields_ = [('hwnd', HWND), ('message', UINT),
('wParam', WPARAM),('lParam', LPARAM),
('time', DWORD), ('pt', POINT), ('lPrivate', DWORD)]
class RECT(ctypes.Structure):
_fields_ = [('left', INT), ('top', INT), ('right', INT), ('bottom', INT)]
class BITMAPFILEHEADER(ctypes.Structure):
_pack_ = 2
_fields_ = [('bfType', WORD), ('bfSize', DWORD),
('bfReserved1', WORD), ('bfReserved2', WORD), ('bfOffBits', DWORD)]
class BITMAPINFOHEADER(ctypes.Structure):
_pack_ = 2
_fields_ = [('biSize', DWORD), ('biWidth', LONG), ('biHeight', LONG),
('biPlanes', WORD), ('biBitCount', WORD),
('biCompression', DWORD), ('biSizeImage', DWORD),
('biXPelsPerMeter', LONG), ('biYPelsPerMeter', LONG),
('biClrUsed', DWORD), ('biClrImportant', DWORD)]
class BITMAPINFO(ctypes.Structure):
_pack_ = 2
_fields_ = [('bmiHeader', BITMAPINFOHEADER), ('bmiColors', DWORD * 1)]
def createColorBitmap(img):
bio = BytesIO()
img.save(bio, 'BMP')
bio.seek(0, 0)
bfhbuf = bio.read(ctypes.sizeof(BITMAPFILEHEADER))
bihbuf = bio.read(ctypes.sizeof(BITMAPINFOHEADER))
bmbuf = bio.read()
bih = BITMAPINFOHEADER(*struct.unpack('<LLLHHLLLLLL', bihbuf))
bi = BITMAPINFO(bih, (DWORD * 1)(0))
pBits = ctypes.c_char_p(0) # keep it
hbmp = gdi32.CreateDIBSection(0,
ctypes.byref(bi), DIB_RGB_COLORS, ctypes.byref(pBits), 0, 0)
gdi32.SetDIBits(0, hbmp, 0, bih.biHeight, bmbuf,
ctypes.byref(bi), DIB_RGB_COLORS)
return hbmp
pClassName = 'OneHourTetrisClass'
pAppName = 'OneHourTetris'
hInst = 0
hMainWindow = 0
hImgBmp = 0
hMemDC, hBlockDC = 0, 0
hMemPrev, hBlockPrev = 0, 0
MY_TIMER_ID = 100
MY_FRAME_RATE = 1000 // 30
PPC = 8
DOT = 4 * PPC
ROT = 4
POS = 3
XWIDTH = 10
YHEIGHT = 20
DXWIDTH = DOT * XWIDTH
DYHEIGHT = DOT * YHEIGHT
XMAX = XWIDTH + 2
YMAX = YHEIGHT + 5
class STATUS(ctypes.Structure):
_fields_ = [('x', INT), ('y', INT), ('type', INT), ('rotate', INT)]
class POSITION(ctypes.Structure):
_fields_ = [('x', INT), ('y', INT)]
class BLOCK(ctypes.Structure):
_fields_ = [('rotate', INT), ('p', POSITION * 3)]
block = [
BLOCK(1, ((0, 0), (0, 0), (0, 0))), # null
BLOCK(2, ((0, -1), (0, 1), (0, 2))), # tetris
BLOCK(4, ((0, -1), (0, 1), (1, 1))), # L1
BLOCK(4, ((0, -1), (0, 1), (-1, 1))), # L2
BLOCK(2, ((0, -1), (1, 0), (1, 1))), # key1
BLOCK(2, ((0, -1), (-1, 0), (-1, 1))), # key2
BLOCK(1, ((0, 1), (1, 0), (1, 1))), # square
BLOCK(4, ((0, -1), (1, 0), (-1, 0)))] # T
current = STATUS(0, 0, 0, 0)
board = [([0] * XMAX) for _ in range(YMAX)]
timer_w = 0
def putBlock(s, action=False):
'''s: STATUS, action: bool'''
global board
if board[s.y][s.x] != 0: return False
if action: board[s.y][s.x] = s.type
for i in range(POS):
dx = block[s.type].p[i].x
dy = block[s.type].p[i].y
r = s.rotate % block[s.type].rotate
for j in range(r):
nx, ny = dx, dy
dx, dy = ny, -nx
if board[s.y + dy][s.x + dx] != 0: return False
if action: board[s.y + dy][s.x + dx] = s.type
if not action: putBlock(s, True)
return True
def deleteBlock(s):
'''s: STATUS'''
global board
board[s.y][s.x] = 0
for i in range(POS):
dx = block[s.type].p[i].x
dy = block[s.type].p[i].y
r = s.rotate % block[s.type].rotate
for j in range(r):
nx, ny = dx, dy
dx, dy = ny, -nx
board[s.y + dy][s.x + dx] = 0
return True
def newBlock():
global current
current.x = XWIDTH // 2
current.y = YHEIGHT + 1
current.type = np.random.randint(len(block) - 1) + 1
current.rotate = np.random.randint(ROT)
def initBoard():
global current, board
for y in range(YMAX):
for x in range(XMAX):
board[y][x] = 1 if x == 0 or x == (XMAX - 1) or y == 0 else 0
newBlock()
putBlock(current)
def gameOver():
global board
user32.KillTimer(hMainWindow, MY_TIMER_ID)
for y in range(1, YHEIGHT + 1):
for x in range(1, XWIDTH + 1):
if board[y][x] != 0: board[y][x] = 1
user32.InvalidateRect(hMainWindow, 0, 0) # 0, False
def deleteLine():
global board
for y in range(1, YMAX - 2):
flg = True
while flg:
for x in range(1, XWIDTH + 1):
if board[y][x] == 0: flg = False
if not flg: break
for j in range(y, YMAX - 2):
for i in range(1, XWIDTH + 1):
board[j][i] = board[j + 1][i]
def blockDown():
global current
deleteBlock(current)
current.y -= 1
if not putBlock(current):
current.y += 1
putBlock(current)
deleteLine()
newBlock()
if not putBlock(current): gameOver()
def showBoard():
global board
for y in range(1, YHEIGHT + 1):
for x in range(1, XWIDTH + 1):
gdi32.StretchBlt(hMemDC, (x - 1) * DOT, (YHEIGHT - y) * DOT, DOT, DOT,
hBlockDC, 0, board[y][x] * PPC, PPC, PPC, SRCCOPY)
def processInput():
global current
ret = False
n = copy.deepcopy(current)
if user32.GetAsyncKeyState(VK_LEFT): n.x -= 1
elif user32.GetAsyncKeyState(VK_RIGHT): n.x += 1
elif user32.GetAsyncKeyState(VK_UP): n.rotate += 1
elif user32.GetAsyncKeyState(VK_DOWN): ret = True
if n.x != current.x or n.y != current.y or n.rotate != current.rotate:
deleteBlock(current)
if putBlock(n): current = copy.deepcopy(n)
else: putBlock(current)
return ret
def WndProc(hwnd, msg, wp, lp):
global hMemDC, hBlockDC, hMemPrev, hBlockPrev, timer_w
if msg == WM_CREATE:
np.random.seed(int(time.time()))
np.random.randint(65536)
initBoard()
hdc = user32.GetDC(hwnd)
hMemDC = gdi32.CreateCompatibleDC(hdc)
hbmp = gdi32.CreateCompatibleBitmap(hdc, DXWIDTH, DYHEIGHT)
hMemPrev = gdi32.SelectObject(hMemDC, hbmp)
hBlockDC = gdi32.CreateCompatibleDC(hdc)
hBlockPrev = gdi32.SelectObject(hBlockDC, hImgBmp)
user32.ReleaseDC(hwnd, hdc)
elif msg == WM_DESTROY:
hbmp = gdi32.SelectObject(hMemDC, hMemPrev)
gdi32.DeleteObject(hbmp)
gdi32.DeleteObject(hMemDC)
hbmp = gdi32.SelectObject(hBlockDC, hBlockPrev)
gdi32.DeleteObject(hbmp)
gdi32.DeleteObject(hBlockDC)
user32.PostQuitMessage(0)
elif msg == WM_PAINT:
showBoard()
hdc = user32.GetDC(hwnd)
gdi32.BitBlt(hdc, 0, 0, DXWIDTH, DYHEIGHT, hMemDC, 0, 0, SRCCOPY)
user32.ReleaseDC(hwnd, hdc)
elif msg == WM_TIMER:
if timer_w % 2 == 0:
if processInput(): timer_w = 0
if timer_w % 5 == 0:
blockDown()
timer_w += 1
user32.InvalidateRect(hwnd, 0, 0) # 0, False
return DefWndProc(hwnd, msg, wp, lp)
def winmain(img):
global hInst, hMainWindow, hImgBmp
hInst = kernel32.GetModuleHandleA(0)
hImgBmp = createColorBitmap(img)
wc = WNDCLASSEX(ctypes.sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW,
WNDPROC(WndProc), 0, 0, hInst, 0, user32.LoadCursorW(0, IDC_ARROW),
COLOR_WINDOW + 1, 0, pClassName,
user32.LoadImageW(hInst, pAppName, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR))
if user32.RegisterClassExW(ctypes.byref(wc)) == 0:
print('error: RegisterClassExW')
return False
r = RECT(0, 0, DXWIDTH, DYHEIGHT)
user32.AdjustWindowRectEx(ctypes.byref(r),
WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU | WS_CAPTION,
0, 0) # False, 0
hMainWindow = user32.CreateWindowExW(WS_EX_OVERLAPPEDWINDOW,
pClassName, pAppName,
WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU | WS_CAPTION,
CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top,
0, 0, hInst, 0)
if hMainWindow == 0:
print('error: CreateWindowW')
return False
user32.ShowWindow(hMainWindow, SW_SHOW)
user32.SetTimer(hMainWindow, MY_TIMER_ID, MY_FRAME_RATE, 0)
msg = MSG()
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0:
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
user32.KillTimer(hMainWindow, MY_TIMER_ID)
gdi32.DeleteObject(hImgBmp)
return True
winmain(loadpng(PNG))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment