Spectrum-Environment for Pygame
Spectrum-Environment for Pygame
Hi there,
a Spectrum-esque environment in Python/Pygame:
Although it's amazing to program the real (or emulated) Spectrum in C, for me it's also rather exhausting.
On the other hand, I know Python quite well, and Pygame would be ok.
I've never seen a really good game in Pygame, though. Maybe it needs some limitations to give it a frame.
dfzx pointed out, that in games programming on the Spectrum, you mostly poke everything directly into the screen buffer. I though, maybe I could do that in Pygame and make it look (and feel) really Spectrum-like.
Well, that's how far I've come (in a few days yet):
- Can set border and paper,
- can plot something (256x176),
- can set coloured paper-spots (32x22),
- can write like "PRINT AT".
I'm not sure, how fast this is (fast enough for "Manic Miner"?). But hey, if a 3.5 Mhz Z80 can beat your libraries on today's Ghz-PC, the libraries wouldn't be worth it anyway.
What do you think?
By the way: Can I get in trouble, when I use the Spectrum's charset (stripped from its ROM), its look-and-feel or the words "Spectrum" and "ZX" in my code?
a Spectrum-esque environment in Python/Pygame:
Although it's amazing to program the real (or emulated) Spectrum in C, for me it's also rather exhausting.
On the other hand, I know Python quite well, and Pygame would be ok.
I've never seen a really good game in Pygame, though. Maybe it needs some limitations to give it a frame.
dfzx pointed out, that in games programming on the Spectrum, you mostly poke everything directly into the screen buffer. I though, maybe I could do that in Pygame and make it look (and feel) really Spectrum-like.
Well, that's how far I've come (in a few days yet):
- Can set border and paper,
- can plot something (256x176),
- can set coloured paper-spots (32x22),
- can write like "PRINT AT".
I'm not sure, how fast this is (fast enough for "Manic Miner"?). But hey, if a 3.5 Mhz Z80 can beat your libraries on today's Ghz-PC, the libraries wouldn't be worth it anyway.
What do you think?
By the way: Can I get in trouble, when I use the Spectrum's charset (stripped from its ROM), its look-and-feel or the words "Spectrum" and "ZX" in my code?
Re: Spectrum-Environment for Pygame
The exercise from here.
Remember: The screenshot doesn't show an emulator-screen, but a Pygame-screen.
Code-snippet is as easy as this:
UDG-definition was much easier than expected: Just added another tuple with 8 numbers to the charset-dictionary with key "chr(144)".
Cool.
Remember: The screenshot doesn't show an emulator-screen, but a Pygame-screen.
Code-snippet is as easy as this:
Code: Select all
self.zxenv.printAt(10, 6, "CONNECT 4")
for i in range(11, 18, 1):
for u in range(10, 16, 1):
self.zxenv.printAt(i, u, chr(144), "blue", "white")
Cool.
Re: Spectrum-Environment for Pygame
I just realized, in my environment, I'm not limited to the Spectrum's memory. So I can have more than just 22 UDGs. In fact, I can have as many UDGs as a Python-dictionary can hold entries. That is, about as many as I want. Thousands.
Re: Spectrum-Environment for Pygame
But... but... but... it's not a Spectrum, is it?
It's an environment on a modern machine which looks a bit like a Spectrum, but uses modern technology to circumvent the restrictions of the original, which in turn removes most of the challenge. And the challenge of getting the most out of the 1980s technology is what keeps us drawn to the machine.
Not that I'm knocking you for what you're doing - whatever floats your goat - but a Spectrum with thousands of UDGs isn't a Spectrum for most of us.
It's an environment on a modern machine which looks a bit like a Spectrum, but uses modern technology to circumvent the restrictions of the original, which in turn removes most of the challenge. And the challenge of getting the most out of the 1980s technology is what keeps us drawn to the machine.
Not that I'm knocking you for what you're doing - whatever floats your goat - but a Spectrum with thousands of UDGs isn't a Spectrum for most of us.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Re: Spectrum-Environment for Pygame
True, it's not a Spectrum ... I'm sorry.
I guess everybody has his own approach to this. You're probably quite happy with the "Spectrum Next" too.
I thought, hmm, 3.5 Mhz, do I want that today, still?
It's more the look and feel that I love. The simplicity of just one set graphics mode. The ability to populate it with text and colour easily, wherever you like. Draw lines with a single command. I always liked that.
(Wasn't that easy on other machines. Think of C64, Amiga or PC. Could you just do a "PAPER 4"? No. That kind of thing.)
And I always wanted to get into Pygame; so this project is a good motivation for me.
You're mileage may vary. That should be ok.
------
By the way: I'm also not limited to the traditional 8x8-matrix. I can draw pixel-art of whatever size, which makes those things sprites, I guess.
Re: Spectrum-Environment for Pygame
As long as you have fun, do it
It's not Spectrum but it has been done before - take Zx Spectrum Basic, take some features of it that you think that are cool, add some new features and program it in some modern language on a modern computer.
There was once a project called SpecBas which did a bit similar stuff:
https://sites.google.com/site/pauldunn/
It's not Spectrum but it has been done before - take Zx Spectrum Basic, take some features of it that you think that are cool, add some new features and program it in some modern language on a modern computer.
There was once a project called SpecBas which did a bit similar stuff:
https://sites.google.com/site/pauldunn/
Re: Spectrum-Environment for Pygame
Interesting project. It would be cool to code Spectrum games in a subset of Python. Learn Python and see what the interpreted results would be like in a real Spectrum without compiling! Who knows if the subset could be assisted by the ide so the result was compatible with cython code which in turn would be compatible with z88dk C
Re: Spectrum-Environment for Pygame
There is a full Spectrum Emulator written in Python and Pygame here;
https://www.pygame.org/project-PyZX-173-.html
https://www.pygame.org/project-PyZX-173-.html
Re: Spectrum-Environment for Pygame
Clivethon would only provide a tiny subset of the huge Python language. Clivethon programs would be able to run in Python 3 so that any knowledge gained in learning Clivethon will transfer directly to learning Python. Besides the teaching scope, there you go the sweet part of the deal, Clivethon programs could be translated into Z80 so coool machine cooode!
Spoiler
You decide if we are indeed Mr Pixel's offspring trying to inspire the world.
There you go come and go some links to inspire the world.
Translate Python into assembler
https://benhoyt.com/writings/pyast64/
DSL with Python
https://dbader.org/blog/writing-a-dsl-with-python
Python-subset interpreter
https://github.com/louisyang2015/interpreter
http://techfeast-hiranya.blogspot.com/2 ... on-on.html
Irony for NET
https://www.codeproject.com/Articles/25 ... t-Compiler
There you go come and go some links to inspire the world.
Translate Python into assembler
https://benhoyt.com/writings/pyast64/
DSL with Python
https://dbader.org/blog/writing-a-dsl-with-python
Python-subset interpreter
https://github.com/louisyang2015/interpreter
http://techfeast-hiranya.blogspot.com/2 ... on-on.html
Irony for NET
https://www.codeproject.com/Articles/25 ... t-Compiler
Re: Spectrum-Environment for Pygame
It's still going: https://www.youtube.com/user/ZXSpin/videosRalf wrote: ↑Fri Feb 15, 2019 5:14 pm As long as you have fun, do it
It's not Spectrum but it has been done before - take Zx Spectrum Basic, take some features of it that you think that are cool, add some new features and program it in some modern language on a modern computer.
There was once a project called SpecBas which did a bit similar stuff:
https://sites.google.com/site/pauldunn/
And still under development.
Re: Spectrum-Environment for Pygame
Hi guys,
a Spectrum emulator in Python would probably be too slow, I think. Tried one written in Perl once. It kind of worked, but it wasn't much fun compared to programs like "fuse".
Pygame gave me a hard time in the meantime. I coded some "Space Invaders"-clone in the Spectrum resolution (because the pixel artwork could be found as images somewhere), but I found, somebody else did it all way better than me, so I'm trying to learn from his code at the moment. It may still take some time, but there's some progress.
Here's something, I can already show you (I use Python 2.7 and Pygame 1.9.1, so there may be problems on other configurations). Anyway, here it goes ('q' left, 'w' right, "right shift" jump, 'a' end). Hope you like it . To get the movement accurate, is actually quite tricky. I'd say, I'm about 90% there at the moment:
In the end, you will need an idea for an interesting game and also the skill to create the artwork for it.
I'm not sure, if I have the will to go through all of that. Back then, they could at least put on a tape, what they had created, and sell it.
a Spectrum emulator in Python would probably be too slow, I think. Tried one written in Perl once. It kind of worked, but it wasn't much fun compared to programs like "fuse".
Pygame gave me a hard time in the meantime. I coded some "Space Invaders"-clone in the Spectrum resolution (because the pixel artwork could be found as images somewhere), but I found, somebody else did it all way better than me, so I'm trying to learn from his code at the moment. It may still take some time, but there's some progress.
Here's something, I can already show you (I use Python 2.7 and Pygame 1.9.1, so there may be problems on other configurations). Anyway, here it goes ('q' left, 'w' right, "right shift" jump, 'a' end). Hope you like it . To get the movement accurate, is actually quite tricky. I'd say, I'm about 90% there at the moment:
Code: Select all
#!/usr/bin/python
# coding: utf-8
import pygame
from pygame.locals import *
# License for script code: GNU GPL, v.2.
import os
SCALEFACTOR = 3
class ZXEnvironment:
def __init__(self):
self.zx_paperwidth = 256
self.zx_paperheight = 176
self.zx_char_width = self.zx_paperwidth / 8 # 32
self.zx_char_height = self.zx_paperheight / 8 # 22
self.pc_paperwidth = self.zx_paperwidth * SCALEFACTOR
self.pc_paperheight = self.zx_paperheight * SCALEFACTOR
self.zx_border = 16 # 2
self.pc_border = self.zx_border * SCALEFACTOR
self.screenwidth = (self.zx_paperwidth + 2 * self.zx_border) * SCALEFACTOR
self.screenheight = (self.zx_paperheight + 2 * self.zx_border) * SCALEFACTOR
self.data = ZXData()
self.colours = self.data.colours
def initPaper(self, screen):
self.screen = screen
self.paper = pygame.Surface((self.pc_paperwidth, self.pc_paperheight))
self.paperpos = (self.pc_border, self.pc_border)
self.cls()
def cls(self, do_blit = 1, colourname = "white"):
self.paper.fill(self.colours[colourname])
self.papercolourname = colourname
if do_blit:
self.screen.blit(self.paper, self.paperpos)
def border(self, colourname):
bars = (pygame.Surface((self.screenwidth, self.pc_border)),
pygame.Surface((self.pc_border, self.pc_paperheight)))
pos = { 0 : ((0, 0), (0, self.pc_border + self.pc_paperheight)),
1 : ((0, self.pc_border),
(self.pc_border + self.pc_paperwidth, self.pc_border))}
for i in range(len(bars)):
bars[i].fill(self.colours[colourname])
for u in pos[i]:
self.screen.blit(bars[i], u)
self.bordercolourname = colourname
def zxToPCxy(self, zx_x, zx_y):
x = zx_x * SCALEFACTOR
y = zx_y * SCALEFACTOR
return (x, y)
class Main:
def __init__(self):
os.environ['SDL_VIDEO_WINDOW_POS'] = "218, 5"
self.zxenv = ZXEnvironment()
pygame.init()
self.screen = pygame.display.set_mode((self.zxenv.screenwidth, self.zxenv.screenheight))
pygame.display.set_caption('Hello Willy!')
self.keys = None
self.zxenv.initPaper(self.screen)
self.zxenv.cls(1, "black")
self.bordercolour = "red"
self.zxenv.border(self.bordercolour)
self.initSprites()
self.run()
def run(self):
while True:
self.clock = pygame.time.Clock()
self.clock.tick(10)
self.zxenv.cls(do_blit = 0, colourname = self.zxenv.papercolourname)
r = self.processEvents()
if r == "quit":
return r
self.mw.update(self.zxenv.paper, self.keys)
self.screen.blit(self.zxenv.paper, self.zxenv.paperpos)
pygame.display.flip()
def processEvents(self):
self.keys = []
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return "quit"
if event.type == pygame.KEYDOWN:
self.keys.append(event.key)
self.keys = pygame.key.get_pressed()
if self.keys[K_a]:
pygame.quit()
return "quit"
return 0
def initSprites(self):
self.mw = Willy("Willy", 30, 80, self.zxenv, "white")
class UDGSprite(pygame.sprite.Sprite):
def __init__(self, name, zx_x, zx_y, zxenv, inkcolourname):
pygame.sprite.Sprite.__init__(self)
self.name = name
self.zxenv = zxenv
self.zx_position = (zx_x, zx_y)
self.pc_position = self.zxenv.zxToPCxy(zx_x, zx_y)
self.colours = self.zxenv.colours
self.inkcolourname = inkcolourname
self.data = SpriteData()
self.imagedata = self.data.getImagedata(self.name)
self.images = []
self.zx_imagesizes = []
self.pc_imagesizes = []
self.createImages()
self.currentimage = 0
self.image = None
self.rect = None
self.active = False
self.xmoved = 0
self.ymoved = 0
def setImage(self, imagenumber):
self.currentimage = imagenumber
self.image = self.images[imagenumber]
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[imagenumber])
def zx_moveTo(self, zx_x, zx_y):
self.zx_position = (zx_x, zx_y)
self.pc_position = self.zxenv.zxToPCxy(zx_x, zx_y)
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[self.currentimage])
def zx_moveBy(self, move_zx_x, move_zx_y):
self.zx_position = (self.zx_position[0] + move_zx_x,
self.zx_position[1] + move_zx_y)
self.pc_position = (self.pc_position[0] + move_zx_x * SCALEFACTOR,
self.pc_position[1] + move_zx_y * SCALEFACTOR)
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[self.currentimage])
def createImages(self):
a = self.imagedata.keys()
a.sort()
for i in a:
b = i.split("_")
if len(b) == 1:
num = 0
else:
num = int(b[1])
b = []
numstr = "{0:0" + str(self.imagedata[i][0]) + "b}"
for u in self.imagedata[i][1]:
binstring = numstr.format(u)
b.append(binstring)
zx_spritesize = (len(b[0]), len(b))
pc_spritesize = (zx_spritesize[0] * SCALEFACTOR,
zx_spritesize[1] * SCALEFACTOR)
surface = pygame.Surface(pc_spritesize)
surface = pygame.Surface.convert_alpha(surface)
surface = self.plotImage(surface, b, zx_spritesize[0], self.inkcolourname)
self.images.append(surface)
self.zx_imagesizes.append(zx_spritesize)
self.pc_imagesizes.append(pc_spritesize)
def plotImage(self, surface, data_, spritewidth, inkcolourname):
pxarray = pygame.PixelArray(surface)
t_x = 0
t_y = 0
for line in data_:
for bit in line:
if bit == "1":
# Plot one ZX pixel:
for pixelline in range(SCALEFACTOR):
for pixelrow in range(SCALEFACTOR):
pxarray[t_x * SCALEFACTOR + pixelline][t_y * SCALEFACTOR + pixelrow] = self.colours[inkcolourname]
else:
# Plot one ZX pixel:
for pixelline in range(SCALEFACTOR):
for pixelrow in range(SCALEFACTOR):
pxarray[t_x * SCALEFACTOR + pixelline][t_y * SCALEFACTOR + pixelrow] = (0, 0, 0, 0)
t_x += 1
# Next line (like a typewriter):
t_y += 1
t_x -= spritewidth
del pxarray
return surface
class Willy(UDGSprite):
def __init__(self, name, zx_x, zx_y, zxenv, inkcolourname):
UDGSprite.__init__(self, name, zx_x, zx_y, zxenv, inkcolourname)
# Willy's movement is actually quite strange:
self.imagepos = (0, 2, 0, 1, 3, 4, 3, 5)
self.moves = (3, 3, 1, 1, 3, 3, 1, 1)
self.ind = 0
self.lifted = (-3, -4, -8, -4, -3, 0, 0, 0, 0, 3, 4, 8, 4, 3)
self.jumping = False
self.lindex = 0
self.direction = 0
def update(self, surface, keys, *args):
self.setImage(self.imagepos[self.ind])
if self.jumping:
self.jump()
surface.blit(self.image, self.pc_position)
return
if keys[K_q]:
self.direction = -1
self.go_left()
if keys[K_w]:
self.direction = 1
self.go_right()
if not keys[K_q] and not keys[K_w]:
self.direction = 0
if keys[K_RSHIFT]:
self.jumping = True
surface.blit(self.image, self.pc_position)
def go_left(self):
if self.ind < 4:
self.ind += 4
if self.zx_position[0] > 0:
self.zx_moveBy(- self.moves[self.ind], 0)
self.ind += 1
if self.ind > 7:
self.ind = 4
def go_right(self):
if self.ind > 3:
self.ind -= 4
if self.zx_position[0] < self.zxenv.zx_paperwidth - self.zx_imagesizes[self.currentimage][0]:
self.zx_moveBy(self.moves[self.ind], 0)
self.ind += 1
if self.ind > 3:
self.ind = 0
def jump(self):
if self.direction == -1:
self.go_left()
elif self.direction == 1:
self.go_right()
self.zx_moveBy(0, self.lifted[self.lindex])
self.lindex += 1
if self.lindex > len(self.lifted) - 1:
self.lindex = 0
self.jumping = 0
class ZXData:
def __init__(self):
self.colours = {"black" : (0, 0, 0),
"blue" : (0, 0, 197),
"red" : (189, 0, 0),
"magenta" : (189, 0, 197),
"green" : (0, 190, 0),
"cyan" : (0, 190, 197),
"yellow" : (189, 190, 0),
"white" : (189, 190, 197)}
class SpriteData:
def __init__(self):
self.imagedata = {'Willy_0' : (8, (6, 62, 124, 52, 62, 60, 24, 60, 126, 126, 247, 251, 60, 118, 110, 119)),
'Willy_1' : (10, (12, 124, 248, 104, 124, 120, 48, 120, 252, 510, 1023, 891, 124, 237, 391, 454)),
'Willy_2' : (6, (3, 31, 62, 26, 31, 30, 12, 30, 55, 55, 55, 59, 30, 12, 12, 14)),
'Willy_3' : (8, (96, 124, 62, 44, 124, 60, 24, 60, 126, 126, 239, 223, 60, 110, 118, 238)),
'Willy_4' : (10, (192, 248, 124, 88, 248, 120, 48, 120, 252, 510, 1023, 891, 248, 732, 902, 398)),
'Willy_5' : (6, (48, 62, 31, 22, 62, 30, 12, 30, 59, 59, 59, 55, 30, 12, 12, 28))}
def getImagedata(self, spritename):
imagedataslice = {}
for i in self.imagedata.keys():
if i.startswith(spritename):
imagedataslice[i] = self.imagedata[i]
return imagedataslice
if __name__ == '__main__':
Main()
I'm not sure, if I have the will to go through all of that. Back then, they could at least put on a tape, what they had created, and sell it.
Re: Spectrum-Environment for Pygame
Hmm, no one tried it. Oh well.
I just realized, I can't directly use a "PRINT AT" in Pygame. Because the screen is updated every frame. It is cleared and reprinted many times per second. It wouldn't be good, to reprint the data that often.
It's better to create the writing once on a Pygame Surface, and then reblit that surface each frame. I call that a "StringSprite". It's the same result, but a bit more complicated than such a simple command like in the old Basic.
It's pretty cool though, that in Pygame you can create a transparent surface, that holds just the "INK" and uses, whatever "PAPER" is below it.
Oh, this may be interesting for you: In my example, Willy couldn't be kept inside the screen sometimes, because the graphics have different widths (8, 10, 6 Bit). When I looked at the original game, there's a small additional border on both sides of the screen. Probably to solve that problem. This border is also carefully and beautifully designed, with different colours and patterns in each level. Nice.
I just realized, I can't directly use a "PRINT AT" in Pygame. Because the screen is updated every frame. It is cleared and reprinted many times per second. It wouldn't be good, to reprint the data that often.
It's better to create the writing once on a Pygame Surface, and then reblit that surface each frame. I call that a "StringSprite". It's the same result, but a bit more complicated than such a simple command like in the old Basic.
It's pretty cool though, that in Pygame you can create a transparent surface, that holds just the "INK" and uses, whatever "PAPER" is below it.
Oh, this may be interesting for you: In my example, Willy couldn't be kept inside the screen sometimes, because the graphics have different widths (8, 10, 6 Bit). When I looked at the original game, there's a small additional border on both sides of the screen. Probably to solve that problem. This border is also carefully and beautifully designed, with different colours and patterns in each level. Nice.
Re: Spectrum-Environment for Pygame
Heya,
still was impressed by the Z80 assembly tutorial on Youtube by Darryl Sloan. He's demonstrating "Connect 4", using a single UDG in different colours:
Z80 assembly Youtube-tutorial by Darryl Sloan
I wondered, if I could do that in Pygame. It seems, I could. Here's my result.
Keys are: "q" (or "o") - left, "w" (or "p") right, "space" - drop, "u" - undo move, "r" - restart game:
I'm quite happy with that. It's also good to see, the forum lets me post a slightly longer piece of code.
still was impressed by the Z80 assembly tutorial on Youtube by Darryl Sloan. He's demonstrating "Connect 4", using a single UDG in different colours:
Z80 assembly Youtube-tutorial by Darryl Sloan
I wondered, if I could do that in Pygame. It seems, I could. Here's my result.
Keys are: "q" (or "o") - left, "w" (or "p") right, "space" - drop, "u" - undo move, "r" - restart game:
Code: Select all
#!/usr/bin/python
# coding: utf-8
import pygame
from pygame.locals import *
import os
import zlib
import base64
""" winwithfour.py, 1.0:
A little game in Python/Pygame (inspired by a very good Z80 Assembly
tutorial on Youtube).
Copyright 2019, Forum-name: Major Percival FFoulkes, GNU GPL v.2,
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
SCALEFACTOR = 3
FPS = 50
GRIDWIDTH = 7
GRIDHEIGHT = 6
MATCHES = 4
class ZXEnvironment:
def __init__(self):
self.zx_paperwidth = 256
self.zx_paperheight = 176
self.zx_char_width = self.zx_paperwidth / 8 # 32
self.zx_char_height = self.zx_paperheight / 8 # 22
self.pc_paperwidth = self.zx_paperwidth * SCALEFACTOR
self.pc_paperheight = self.zx_paperheight * SCALEFACTOR
self.zx_border = 16 # 2
self.pc_border = self.zx_border * SCALEFACTOR
self.screenwidth = (self.zx_paperwidth + 2 * self.zx_border) * SCALEFACTOR
self.screenheight = (self.zx_paperheight + 2 * self.zx_border) * SCALEFACTOR
self.data = Data()
self.colours = self.data.colours
def initBorderAndPaper(self, screen):
self.screen = screen
self.paper = pygame.Surface((self.pc_paperwidth, self.pc_paperheight))
self.paperrect = self.paper.get_rect(topleft = (self.pc_border, self.pc_border))
self.borderbars = (pygame.Surface((self.pc_border, self.screenheight)),
pygame.Surface((self.pc_paperwidth, self.pc_border)))
self.paperborder = pygame.Surface((self.pc_paperwidth, self.pc_border))
self.paperborderrect = self.paperborder.get_rect(topleft = (self.pc_border, self.pc_border + self.pc_paperheight))
self.borderpositions = {0 : ((0, 0), (self.pc_border + self.pc_paperwidth, 0)),
1 : ((self.pc_border, 0),)}
def cls(self, colourname):
self.setPaperColour(colourname)
self.blitPaperToScreen()
def border(self, colourname):
self.setBorderColour(colourname)
self.blitBorderToScreen()
def setBorderColour(self, colourname):
for b in self.borderbars:
b.fill(self.colours[colourname])
self.paperborder.fill(self.colours[colourname])
self.bordercolourname = colourname
def setPaperColour(self, colourname):
self.paper.fill(self.colours[colourname])
self.papercolourname = colourname
def blitPaperToScreen(self):
""" If you drawed on the paper, you'd have to call "fill()"
to get it clean again. Instead, you blit the (clean) paper
and keep drawing on top of it onto the screen. """
self.screen.blit(self.paper, self.paperrect)
def blitPaperborderToScreen(self):
self.screen.blit(self.paperborder, self.paperborderrect)
def blitBorderToScreen(self):
for i in range(len(self.borderbars)):
for u in self.borderpositions[i]:
self.screen.blit(self.borderbars[i], u)
self.screen.blit(self.paperborder, self.paperborderrect)
def zxToPCxy(self, zx_x, zx_y):
x = self.pc_border + zx_x * SCALEFACTOR
y = self.pc_border + zx_y * SCALEFACTOR
return (x, y)
class StringSprite(pygame.sprite.Sprite):
def __init__(self, zx_x, zx_y, zxenv, image):
pygame.sprite.Sprite.__init__(self)
self.zxenv = zxenv
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = self.zxenv.zxToPCxy(zx_x, zx_y)
def draw(self, surface):
surface.blit(self.image, self.rect)
class UDGSprite(pygame.sprite.Sprite):
def __init__(self, name, zx_x, zx_y, zxenv, colourname):
pygame.sprite.Sprite.__init__(self)
self.name = name
self.zxenv = zxenv
self.zx_position = (zx_x, zx_y)
self.pc_position = self.zxenv.zxToPCxy(zx_x, zx_y)
self.colourname = colourname
self.colour = self.zxenv.colours[colourname]
self.transparent = (0, 0, 0, 0)
self.data = Data()
self.images = []
self.zx_imagesizes = []
self.pc_imagesizes = []
self.currentimage = 0
self.image = None
self.rect = None
self.createImages()
def setImage(self, imagenumber):
self.currentimage = imagenumber
self.image = self.images[imagenumber]
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[imagenumber])
def moveTo(self, zx_x, zx_y):
self.zx_position = (zx_x, zx_y)
self.pc_position = self.zxenv.zxToPCxy(zx_x, zx_y)
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[self.currentimage])
def moveBy(self, move_zx_x, move_zx_y):
self.zx_position = (self.zx_position[0] + move_zx_x,
self.zx_position[1] + move_zx_y)
self.pc_position = (self.pc_position[0] + move_zx_x * SCALEFACTOR,
self.pc_position[1] + move_zx_y * SCALEFACTOR)
self.rect = pygame.Rect(self.pc_position,
self.pc_imagesizes[self.currentimage])
def draw(self, surface):
surface.blit(self.image, self.pc_position)
def createImages(self):
# Format expected by imagedata: {'UDG1_0': (8, (1, 2, 3, 4)),
# 'UDG1_1': (8, (5, 6, 7, 8))}
imagedata = self.data.getImagedata(self.name)
a = imagedata.keys()
a.sort()
for i in a:
b = []
numstr = "{0:0" + str(imagedata[i][0]) + "b}"
for u in imagedata[i][1]:
binstring = numstr.format(u)
b.append(binstring)
self.addImage(b, self.colourname)
def addImage(self, binstringlist, colourname):
zx_spritesize = (len(binstringlist[0]), len(binstringlist))
pc_spritesize = (zx_spritesize[0] * SCALEFACTOR,
zx_spritesize[1] * SCALEFACTOR)
surface = pygame.Surface(pc_spritesize)
surface = surface.convert_alpha(surface)
surface = self.plotImage(surface, binstringlist, zx_spritesize[0], colourname)
self.images.append(surface)
self.zx_imagesizes.append(zx_spritesize)
self.pc_imagesizes.append(pc_spritesize)
def plotImage(self, surface, data_, spritewidth, colourname):
pxarray = pygame.PixelArray(surface)
t_x = 0
t_y = 0
for line in data_:
for bit in line:
if bit == "1":
plotcolour = self.zxenv.colours[colourname]
else:
plotcolour = self.transparent
# Plot one ZX pixel:
for pixelline in range(SCALEFACTOR):
for pixelrow in range(SCALEFACTOR):
pxarray[t_x * SCALEFACTOR + pixelline][t_y * SCALEFACTOR + pixelrow] = plotcolour
t_x += 1
# Next line (like a typewriter):
t_y += 1
t_x -= spritewidth
del pxarray
return surface
class GridSprite(UDGSprite):
def __init__(self, name, zx_x, zx_y, zxenv, colourname):
self.zx_x = zx_x
self.zx_y = zx_y
UDGSprite.__init__(self, name, zx_x, zx_y, zxenv, colourname)
def createImages(self):
imagedata = self.data.getImagedata(self.name)
b = []
for i in range(GRIDHEIGHT):
for u in imagedata:
bstr = "{0:08b}".format(u)
bstr *= GRIDWIDTH
b.append(bstr)
self.addImage(b, "blue")
class Tile(UDGSprite):
def __init__(self, name, zx_x, zx_y, zxenv, colourname, grid_x, grid_y, gridzero_y):
UDGSprite.__init__(self, name, zx_x, zx_y, zxenv, colourname)
self.colourname = colourname
self.grid_x = grid_x
self.grid_y = grid_y
self.gridzero_y = gridzero_y
self.active = False
self.moveable = False
self.falling = 0
def setMoveable(self, zx_to_x, zx_to_y):
self.active = True
self.moveable = True
self.moveTo(zx_to_x, zx_to_y)
self.grid_x = 3
self.grid_y = -1
def setImmoveable(self):
self.moveable = False
def setActive(self):
self.active = True
def setInactive(self):
self.active = False
def createImages(self):
imagedata = self.data.getImagedata(self.name)
b = []
for i in imagedata:
bstr = "{0:08b}".format(i)
b.append(bstr)
self.addImage(b, self.colourname)
def moveSideways(self, direction):
if not self.moveable:
return
if direction == "left":
if self.grid_x > 0:
self.grid_x -= 1
self.moveBy(-8, 0)
if direction == "right":
if self.grid_x < GRIDWIDTH - 1:
self.grid_x += 1
self.moveBy(8, 0)
def setTimer(self):
self.timer = pygame.time.get_ticks()
def update(self):
if self.moveable and self.falling == 1:
zx_ydest = self.gridzero_y + self.grid_y * 8
if self.zx_position[1] < zx_ydest:
self.moveBy(0, 1)
else:
self.falling = 2
self.setImmoveable()
def draw(self, surface):
if self.active:
surface.blit(self.image, self.pc_position)
class WinTile(UDGSprite):
def __init__(self, name, zx_x, zx_y, zxenv, colourname, grid_x, grid_y):
self.wincolour = colourname
UDGSprite.__init__(self, name, zx_x, zx_y, zxenv, colourname)
self.grid_x = grid_x
self.grid_y = grid_y
self.active = False
self.delaytime = 600
self.timer = pygame.time.get_ticks()
def setActive(self):
self.active = True
self.timer = pygame.time.get_ticks()
def setInactive(self):
self.active = False
def setWinColour(self, colourname):
self.wincolour = colourname
self.images = []
self.zx_imagesizes = []
self.pc_imagesizes = []
self.createImages()
def createImages(self):
imagedata = self.data.getImagedata(self.name)
b = []
for i in imagedata:
bstr = "{0:08b}".format(i)
b.append(bstr)
self.addImage(b, "magenta")
self.addImage(b, self.wincolour)
def update(self, currenttime):
if not self.active:
return
if currenttime - self.timer > self.delaytime:
self.setImage(1 - self.currentimage)
self.timer += self.delaytime
def draw(self, surface):
if self.active:
surface.blit(self.image, self.pc_position)
class Main:
def __init__(self):
self.zxenv = ZXEnvironment()
os.environ['SDL_VIDEO_WINDOW_POS'] = "218, 5"
pygame.init()
self.screen = pygame.display.set_mode((self.zxenv.screenwidth, self.zxenv.screenheight))
self.clock = pygame.time.Clock()
pygame.display.set_caption('Win with Four')
self.zxenv.initBorderAndPaper(self.screen)
self.data = Data()
self.combinations = self.initCombinations()
self.initGame()
# Main loop:
while True:
self.clock.tick(FPS)
self.timer = pygame.time.get_ticks()
r = self.processEvents()
if r == "quit":
pygame.quit()
return
if r == "replay":
self.initGame()
continue
self.zxenv.blitPaperToScreen()
self.drawMessages("headline")
for s in self.activetiles:
s.update()
for s in self.redtiles:
s.draw(self.zxenv.screen)
for s in self.yellowtiles:
s.draw(self.zxenv.screen)
self.gridSprite.draw(self.zxenv.screen)
if self.activetile.falling == 2:
if self.won:
for s in self.wintiles:
s.update(self.timer)
s.draw(self.zxenv.screen)
if self.won == "red":
self.drawMessages("redwin", "replay")
if self.won == "yellow":
self.drawMessages("yellowwin", "replay")
elif self.outofmoves:
self.drawMessages("tie", "replay")
else:
self.nextTile()
pygame.display.flip()
def initGame(self):
self.moves = 0
self.outofmoves = False
self.won = None
self.initSprites()
self.initGrid()
self.zxenv.cls("white")
self.zxenv.border("red")
for s in self.wintiles:
s.setInactive()
def dropTile(self):
# Selected column is already full:
if self.grid[self.activetile.grid_x][0]:
return
self.activetile.grid_y = 0
while self.activetile.grid_y < GRIDHEIGHT and not self.grid[self.activetile.grid_x][self.activetile.grid_y]:
self.activetile.grid_y += 1
self.activetile.grid_y -= 1
self.activetile.falling = 1
self.activetiles.append(self.activetile)
if self.activetile.colourname == "red":
self.grid[self.activetile.grid_x][self.activetile.grid_y] = 1
if self.activetile.colourname == "yellow":
self.grid[self.activetile.grid_x][self.activetile.grid_y] = 2
self.moves += 1
if self.moves == GRIDWIDTH * GRIDHEIGHT:
self.outofmoves = True
return
# Clever: Don't check every frame, but only, when a tile is dropped:
self.checkGameWon()
if self.won:
return
def undoMove(self):
if self.moves > 0:
self.moves -= 1
self.activetile.setImmoveable()
self.activetile.setInactive()
if self.activetile.colourname == "red":
self.redindex -= 1
if self.activetile.colourname == "yellow":
self.yellowindex -= 1
undotile = self.activetiles[len(self.activetiles) - 1]
undotile.setInactive()
self.grid[undotile.grid_x][undotile.grid_y] = 0
self.activetiles.pop()
self.nextTile()
def nextTile(self):
if self.moves % 2:
self.yellowindex += 1
self.activetile = self.yellowtiles[self.yellowindex]
self.activetile.setMoveable(self.gridSprite.zx_x + 3 * 8, self.gridSprite.zx_y - 8)
else:
self.redindex += 1
self.activetile = self.redtiles[self.redindex]
self.activetile.setMoveable(self.gridSprite.zx_x + 3 * 8, self.gridSprite.zx_y - 8)
def checkGameWon(self):
if self.won or self.outofmoves:
return
for i in self.combinations:
red = 0
yellow = 0
for u in i:
value_at_pos = self.grid[int(u[0])][int(u[1])]
if value_at_pos == 1:
red += 1
if value_at_pos == 2:
yellow += 1
if red == MATCHES:
self.won = "red"
wincoords = i
break
if yellow == MATCHES:
self.won = "yellow"
wincoords = i
break
if self.won:
# At this point, we already know, that someone has won the game,
# but the main loop still has to wait for the last tile to fall:
self.setWinTilesCoords(wincoords)
def setWinTilesCoords(self, coords):
for i in range(len(coords)):
self.wintiles[i].setWinColour(self.won)
self.wintiles[i].moveTo(self.gridSprite.zx_x + 8 * int(coords[i][0]),
self.gridSprite.zx_y + 8 * int(coords[i][1]))
self.wintiles[i].setActive()
def drawMessages(self, *msgnames):
for i in msgnames:
self.messages[i].draw(self.zxenv.screen)
def initCombinations(self):
return self.data.getAllMatchesInAGridCombinations(GRIDWIDTH, GRIDHEIGHT, MATCHES)
def initGrid(self):
self.grid = []
for column in range(GRIDWIDTH):
a = []
for row in range(GRIDHEIGHT):
a.append(0)
self.grid.append(a)
def initSprites(self):
self.gridSprite = GridSprite("Grid", 96, 48, self.zxenv, "blue")
self.gridSprite.setImage(0)
msgpos = {"headline" : (-24, -24),
"redwin" : (-16, 72),
"yellowwin" : (-24, 72),
"tie" : (-72, 72),
"replay" : (-64, 88)}
self.messages = {}
stringimages = self.data.getStringImages()
for i in msgpos.keys():
self.messages[i] = StringSprite(self.gridSprite.zx_x + msgpos[i][0], self.gridSprite.zx_y + msgpos[i][1], self.zxenv, stringimages[i])
n = GRIDWIDTH * GRIDHEIGHT // 2
# A few more tiles than needed (just in case :) ):
n += 5
self.redtiles = []
self.yellowtiles = []
for i in range(n):
tr = Tile("Tile", 0, 0, self.zxenv, "red", -1, -1, self.gridSprite.zx_y)
tr.setImage(0)
self.redtiles.append(tr)
ty = Tile("Tile", 0, 0, self.zxenv, "yellow", -1, -1, self.gridSprite.zx_y)
ty.setImage(0)
self.yellowtiles.append(ty)
self.wintiles = []
for i in range(MATCHES):
t = WinTile("Tile", 0, 0, self.zxenv, "magenta", -1, -1)
t.setImage(0)
self.wintiles.append(t)
self.redindex = 0
self.yellowindex = 0
self.activetile = self.redtiles[self.redindex]
self.activetile.setMoveable(self.gridSprite.zx_x + 3 * 8, self.gridSprite.zx_y - 8)
self.activetiles = [self.activetile]
def processEvents(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "quit"
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_e or event.key == pygame.K_a:
return "quit"
if event.key == pygame.K_r:
return "replay"
if not self.won and not self.outofmoves and self.activetile.falling != 1:
if event.key == pygame.K_q or event.key == pygame.K_o:
self.activetile.moveSideways("left")
if event.key == pygame.K_w or event.key == pygame.K_p:
self.activetile.moveSideways("right")
if event.key == pygame.K_u:
self.undoMove()
if event.key == pygame.K_SPACE:
self.dropTile()
return 0
class Data:
def __init__(self):
self.colours = {"black" : (0, 0, 0),
"blue" : (0, 0, 197),
"red" : (189, 0, 0),
"magenta" : (189, 0, 197),
"green" : (0, 190, 0),
"cyan" : (0, 190, 197),
"yellow" : (189, 190, 0),
"white" : (189, 190, 197)}
self.gridSpriteData = (255, 231, 195, 129, 129, 195, 231, 255)
self.tiledata = (0, 24, 60, 126, 126, 60, 24, 0)
def getImagedata(self, name):
if name == "Grid":
return self.gridSpriteData
if name == "Tile":
return self.tiledata
def getStringImages(self):
msgwidths = {"headline" : 312,
"redwin" : 288,
"yellowwin": 360,
"tie" : 600,
"replay" : 576}
msg = {"headline" : """eJztk8EOwzAIQ/v//7Td9l877ZQpSrAxNHXlUxWBeYbX+/OyLMuyLMuyrMfo+n21jx8oFp9YnW7pXKGv3LaeT+3jB8p3+teP7xTPy3daRZ5ep1s6vlNiXgI+3dJZaVprLMan26rT/Qj4xDzHjHULdzJFyWrl5ZUHqkmUdM7d+MQ8x4x1C3cyRclq5eWVB6pJlHTO3fjEPMeM5ZkHp1BmutVr5U8eHxaNybYo8W75Ye2G8g0YU14vMG46H1Bjr5U/eXxYNEYbdGN0P6zdUL4BY8rrBcZN5wNq7LXyJ48Pi8Zog26M7oe1G8o3YEyCXjFjdD6gxl4rfwTBHc85r47vFDfWf398pxrOeXV8p7ix/vvjO9Vwzqtzxp1ew6c01n9/QCz0KU7lnFdH+YY14GTrwIU8dX9ALPQpTuWcV0f5hjXgZOvAhTx1f0As9ClO5ZxXR/lGOWDMz/gpOdM1NgUPJMZnq/KNONN7sTjT6+SB2moBztVtfyZNa3NfqXwjzvReLM70OnmgtlqAc3Xbn0nT2txXKt+IM70XizO9jgCmZVmWZVmWZd1LX5CqQQk=""",
"redwin" : """eJztlEsSwjAMQ7n/ncqOe8EaZtLE+tQNYrTKRI39LHM8X0cURVEURVEU/eix8ru82q+aN3sr6qxaErJffd6KOiv7dfe3os4C94t12bCet94vFp8mf4NLkTD0NbCDfMAx0WHq5u4Mkq54kI+BM4tPLWO1vgZ2kA84JjpM3dydQdIVD/IxcGbxqWWs1tfADvIBxwRednIGQdX4GMqgM3TyWTrR9a5jaHANLtP7Ogu1KWPOMnTZMPBZOtH1rmNocA0u0/s6C7UpY84ydNkw8Fk60fWuYwi6wCCBdgOfmuj10EHV7Do+Myc6zrr8gC5w7qDdwKcmej10UDW7js/MiY6zLj+gC5w7aDfwqYleDx1Uza7jM3Oi46zLj86lq/nat7rxcTLU8dl1v3Sz6JaNXflkv/rc0fG5tuZ/5pP96nNHx4dV/OC39Bb4HQMfZ19NsMxAqNUD9kWfl9PlDADrO6Do8wL7aoJlBkKtHrAv+rycLmcAWN8BRZ8X2FcTLDMQavWAfdHnBbqiKIqiKIqi6KM3YxbLbw==""",
"yellowwin" : """eJztlDtuxFAMA3P/O2263CvFIpUBA6ZGfFyEBkt9noaEX98/r6qqqqqqqqqqqiR9/X2GriNPDXl8rEqjeutREvrf+Ocqjeqt/jcCHx+r0qje2vsVaH+k6+e8C58zvAvnfJYGzgefY+CjBWCYH5zP0KYQPtRd+BzKd8OlTqoUH3yOgY8WgGF+cD5Dm0L4UHfhcyjfDZc6qVJ88DkGPloAhvnB+eDt2l34C6nY7G3XaobAKRp4DvcYOkX5lVaDkx/GOPYuw3atZgiconHz4bsMLu+9WfMrrQYnP4xx7F2G7VrNEDhF4+bDdxlc3nuz5ldazR754XY8hyF34TVaDinhOaQCgOeH4qP5lVZDdeHbcd9D7sJrtBxSwnNIBQDPD8VH8yuthurCt+O+h9yF12g5pITnkAoAnh+Kj+ZXWg3Vlbb9E+/SarQcOqmezfPeHG2X5ldajZPYXpdz8t5dWo2WQyfVtDyf5aP5lVbjJLbX5Zy8d5dWo+XQSTUtz2f5aH6l1TiJae03HzXZOYc659p1xG48h0Pfh+1OUEPftbtwv4Zd+Jy9AJydQ51z7aKMw12m+DzaRXHeAzX0XbsL92vYhc/ZC8DZOdQ51y7KONxlis+jXRTnPVBD37W7cL+GXVVVVVVVVVVVVSH6BcRUuEE=""",
"tie" : """eJztlVGOgzAQQ3v/O3X/eq89ACodZhzbASN/RQmxnwfx/vu8oyiKoiiKoiiKoiiKoiiKnqTX7Dl5oTxaFHHkNvNaP240nqOQb9PIfzCKhnKb+fwHn6mQb9PIfzCKhnKb+fwHn6mQX0Gj9x54F5f+wky8Q2Pw46ji/Dkza7p0lwlnbXFDzwQ+KGNyaN+sMgOijq/LpaUKL9dkDk/8SObneGpYnD9nZk2X7jLhrC1u6JnAB2VMDu2bVWZA1PF1ubRU4eWazOGJH8n8HE8Ni/PnzKzp0l0mnLXFDT0T+KCMyaF9s8oMiDq+LpcbVRM+62gwyR/3VFaewxme69dQb+ZnXXG92+F8mJG1nHvkCeZ7nVZWmAGHtxNqIlQp8cOcn7tyhuf6NdSb+VlXXO92OB9mZC3nHnmC+V6nlRVmwOHthJoIVUr8MOfnrpzhuX4N9WZ+1hXXux3OhxlZy7lHnmC+12llhRlQO8+ourWdrgtYmZatOQ/99BgS9pjkIjQIPyXJhSqOQBU+SAQ+xz2VFUlASV9DvHChcqECVqZla85DPz2GhD0muQgNwk9JcqGKI1CFDxKBz3FPZUUSUNLXEC9cqFyogJVp2Zrz0E+PIWGPSS5Cg/BTklyo4ghU4YNE4HPcU1mRBJT0xZyxXkBU76g9vflx48ycXrc9zFxuDbrlur2fofl130VlhSntPGuzV4ydPBI+vflx44zyo+3CP5dbg265bu9naH7dd1FZYUo7z9rsFWMnj4RPb37cOKP8aLvwz+XWoFuu2/sZml/3XVRWmDKZ51fhkfDR5oLPjxtnOB9tX7bvIfR16TiqL0Kurf2gzK/zM3QIDwg/1eMz5EwQM1dlbLbmDOej7cv2PYS+Lh1H9UXItbUflPl1foYO4QHhp3p8hpwJYuaqjM3WnOF8tH3ZvofQ16XjqL4Iubb2gzK/zs/QoaSd58jkc4iiKIoiifIfjKIoip6s/AejKIrupH+lC6c3""",
"replay" : """eJztlduRAyEMBDf/nHx/zusCcBUGNEKNPFv9uZLnAevX3/tljDHGGGOMMcYYY4wxQ56Vp1ytGUCrCaIHIgOL8/lNevTu/6820GqC6IHIwOJ8fpMevfv/qw20miB6IDKwOJ/fpEfvSy72Xt77H1T9jdL21DZ4MihILD16H8gI6lGN7+mZeZkWeElxKj15sdTqyXu5ti/antoGTwYFiaVH7wMZQT2q8T09My/TAi8pTqUnL5ZaPXkv1/ZF21Pb4MmgILH06H0gI6hHNb6nZ+ZlWuAlxan05MVSq0f1ctCF6idUYZaUchIb3J46GV2enjwXtLvc1dfSnufjKfF+souT4q8+Gzdig9tTtHua57RWM+2I0nwt7Xk+nhLvJ7s4Kf7qs3EjNrg9RbuneU5rNdOOKM3X0p7n4ynxLu9i5pGHuadHtbBkD40eLuQGZ6ZORpenJ+jiy11VaC45ojRfQT2qVGl9zcSyl3OeHtXCkj00eriQG5yZOhldnp6giy93VaG55IjSfAX1qFKl9TUTy17OeXpUC0v20OjhQm5wZupkdHl6gi6+3FWF5pIjSvMV1KNKldaX/LeCC2/0flIzNgQsze6p6vNFyyfP4NW+5HpovQ+CynORt5B2VmmasSFgod3ToIvBc3U+eQav9iXXQ+t9EFSei7yFtLNK04wNAQvtngZdDJ6r88kzeLUvuR5a74Og8lwcWKjqfW9cvgdLDxdLTlUHoFlN2AtSGzjEl0qPalzeV56evFLy+gqOy/dg6eFiyanqADSrCXtBagOH+FLpUY3L+8rTk1dKXl/BcfkeLD1cLDlVHYBmNWEvSG3gEF8qPapxeV/yeI0xENpf6ry/CfsyxphC2n/0un7nu/oyxphJ2n/0un7nu/qq5R+7zemZ"""}
for i in msg.keys():
msg[i] = base64.b64decode(msg[i])
msg[i] = zlib.decompress(msg[i])
msg[i] = pygame.image.fromstring(msg[i], (msgwidths[i], 24), "RGB")
msg[i] = msg[i].convert(msg[i])
return msg
def getAllMatchesInAGridCombinations(self, width, height, matches):
a = []
# Horizontals:
for row in range(height):
for column in range(width + 1 - matches):
b = []
for u in range(matches):
b.append(str(u + column) + str(row))
a.append(b)
# Verticals:
for column in range(width):
for row in range(height + 1 - matches):
b = []
for u in range(matches):
b.append(str(column) + str(u + row))
a.append(b)
# DiagonalsTopLeftToDownRight:
# -2 to +3:
for d in range(- (height - matches), (width - matches) + 1, 1):
for i in range(width - matches):
b = []
for u in range(matches):
x = i + u
y = i + u
if d > 0:
x += d
if d < 0:
y -= d
if x >= width or y >= height:
break
b.append(str(x) + str(y))
if len(b) == matches:
a.append(b)
# DiagonalsTopRightToDownLeft:
# -3 to +2:
for d in range(- (width - matches), (height - matches) + 1, 1):
for i in range(width - matches):
b = []
for u in range(matches):
x = width - 1 - i - u
y = i + u
if d < 0:
x += d
if d > 0:
y += d
if x < 0 or y >= height:
break
b.append(str(x) + str(y))
if len(b) == matches:
a.append(b)
return a
if __name__ == '__main__':
Main()