Created
January 11, 2019 04:06
-
-
Save nomissbowling/9a5a6fc1468b067aaf438f49cf91096d to your computer and use it in GitHub Desktop.
One Hour Tetris (sm8517855)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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