Created
April 16, 2017 15:06
-
-
Save PM2Ring/3a016965d9f0d8cbfb5addb4219cc725 to your computer and use it in GitHub Desktop.
Flip - an infuriating XOR-based game for Python 2, using GTK2+
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/bin/env python2 | |
''' Classic Flip game, with Help function & selectable goal | |
Written by PM 2Ring 2007.12.22 | |
Updated 2011.08.07 | |
''' | |
import pygtk | |
pygtk.require('2.0') | |
import gtk, sys, random | |
class FlipGame: | |
#Texts for instruction panel | |
labels = ("Make the upper\npattern match\nthe goal at right", | |
" You have lost!! ", " You have won!!") | |
#Which buttons get flipped when we click a button | |
flip = (None, | |
(1,2,4,5), (1,2,3), (2,3,5,6), | |
(1,4,7), (2,4,5,6,8), (3,6,9), | |
(4,5,7,8), (7,8,9), (5,6,8,9)) | |
#Reflect numeric keypad buttons horizontally, to make octal notation intuitive | |
reflect = (0, 3,2,1, 6,5,4, 9,8,7) | |
#Which buttons solve a given button, in octal format | |
keyo = (0, | |
0467, 0552, 0137, | |
0343, 0272, 0616, | |
0764, 0255, 0731) | |
def delete_event(self, widget, event=None): | |
'''This callback quits the program''' | |
gtk.main_quit() | |
return False | |
def setgoal(self, widget, data): | |
'''Toggle single buttons''' | |
if self.playflag: | |
return | |
#Toggle button state | |
widget.isflipped ^= True | |
self.goal ^= 1<<(self.reflect[data] - 1) | |
self.setcolor(widget) | |
def playgame(self, widget, data): | |
'''Toggle all buttons affected by this one.''' | |
#Not permitted to click unflipped buttons | |
if not self.playflag or not widget.isflipped: | |
return | |
for i in self.flip[data]: | |
#Toggle button state | |
self.buttons[i].isflipped ^= True | |
self.status ^= 1<<(self.reflect[i] - 1) | |
#Check status & update instruction panel | |
i = self.status == self.goal and 2 or self.status == 0 and 1 | |
self.label.set_text(self.labels[i]) | |
self.colorsolve() | |
def scramble(self, widget=None): | |
'''Randomize the board. Loop until we aren't | |
in the winning or losing states''' | |
while True: | |
self.status = 0 | |
for i, button in enumerate(self.buttons[1:]): | |
button.isflipped = random.choice([False,True]) | |
if button.isflipped: | |
self.status ^= 1<<(self.reflect[i+1] - 1) | |
if self.status and self.status != self.goal: | |
break | |
self.colorsolve() | |
self.label.set_text(self.labels[0]) | |
def togglehelp(self, widget): | |
self.helpflag = widget.get_active() | |
self.colorsolve() | |
def toggleplay(self, widget): | |
playflag = not widget.get_active() | |
if playflag: | |
if self.goal == 0777: | |
#Goal is impossible, so we don't exit setgoal mode | |
widget.set_active(True) | |
return | |
self.scramble() | |
self.playflag = playflag | |
def setcolor(self, button, helpf=0): | |
''' Set button colors ''' | |
i = button.isflipped + (self.helpflag and helpf and 2) | |
button.modify_bg(gtk.STATE_NORMAL, self.colors[i]) | |
button.modify_bg(gtk.STATE_PRELIGHT, self.colors[4 + i]) | |
def colorsolve(self): | |
'''Color the buttons according to their flip state. If help is enabled | |
also show which ones are needed to transform status to goal''' | |
#print '%03o' % self.status | |
f = self.status ^ self.goal | |
w = lambda t, i: t ^ (((f>>i) & 1) and self.keyo[i+1]) | |
a = reduce(w, xrange(9), 0) | |
for i in xrange(9): | |
self.setcolor(self.buttons[i+1], (a>>i) & 1) | |
def setup_gui(self): | |
# Create a new window | |
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) | |
self.window.set_title("Flip") | |
self.window.set_border_width(4) | |
self.window.set_size_request(250, 400) | |
self.window.connect("delete_event", self.delete_event) | |
# Create some tables for widget layout | |
table = ( | |
gtk.Table(3, 8, False), | |
gtk.Table(3, 3, True), | |
gtk.Table(3, 3, True)) | |
# Put the main table in the window | |
self.window.add(table[0]) | |
#table[0].set_border_width(4) | |
#Insert sub-tables for both number button grids | |
table[0].attach(table[1], 0, 3, 0, 3) | |
table[0].attach(table[2], 2, 3, 4, 5) | |
# Set up handler for keyboard shortcuts | |
accel_group = gtk.AccelGroup() | |
self.window.add_accel_group(accel_group) | |
# Set up button colors | |
colors = ("#f00", "#0d0", "#f88", "#8e8", | |
"#f44", "#4e4", "#faa", "#aea") | |
self.colors = map(gtk.gdk.color_parse, colors) | |
#Widget creation functions | |
def add_button(create_func, labelstr, callback, data, accelkeys, tablenum, location): | |
'''Create a normal or toggle button & add it to the given table ''' | |
# Call function to create the desired button type | |
button = create_func(labelstr) | |
# Connect the callback function | |
if data: | |
button.connect("clicked", callback, data) | |
else: | |
button.connect("clicked", callback) | |
#Add accelerators | |
for key in accelkeys: | |
button.add_accelerator("clicked", accel_group, key, 0, gtk.ACCEL_VISIBLE) | |
# Insert the button into the the table | |
table[tablenum].attach(button, *location) | |
button.show() | |
return button | |
def add_separator(y): | |
''' Add a horizontal separator bar to the main table ''' | |
separator = gtk.HSeparator() | |
table[0].attach(separator, 0, 3, y, y+1) | |
separator.show() | |
def numbutton(n, x, y, tablenum, callback, key): | |
'''Create a button with numeric label n at position (x,y) in given table''' | |
return add_button(gtk.Button, " %d " % n, callback, n, (key,), | |
tablenum, (x, x+1, y, y+1)) | |
# Create a label for instructions & win/lose status | |
self.label = label = gtk.Label(self.labels[0]) | |
table[0].attach(label, 0, 2, 4, 5) | |
label.show() | |
#A list for all the game number buttons. Index 0 is unused | |
self.buttons = 10*[None] | |
# Create both sets of number buttons | |
for i in xrange(3): | |
for j in xrange(3): | |
n = 1 + j + 3*i | |
#Game button, state scrambled below. Accelerator on Numeric keypad | |
self.buttons[n] = numbutton(n, j, 2-i, 1, self.playgame, n + gtk.keysyms.KP_0) | |
#Goal button, state set from goal attribute. Accelerator on main number keys | |
button = numbutton(n, j, 2-i, 2, self.setgoal, n + ord('0')) | |
button.isflipped = (self.goal >> (self.reflect[n] - 1)) & 1 | |
self.setcolor(button) | |
# Put game number buttons into a random state | |
self.scramble() | |
# Add separator bars below the game number buttons and the goal number buttons | |
add_separator(3) | |
add_separator(5) | |
#Add the control buttons | |
add_button(gtk.Button, "_New Game", self.scramble, None, | |
(ord('n'), gtk.keysyms.KP_0), 0, (0, 2, 6, 7)) | |
add_button(gtk.Button, "_Quit", self.delete_event, None, | |
(ord('q'), gtk.keysyms.Escape), 0, (2, 3, 7, 8)) | |
add_button(gtk.ToggleButton, "Set _Goal", self.toggleplay, None, | |
(ord('g'),), 0, (2, 3, 6, 7)) | |
button = add_button(gtk.ToggleButton, "Show _Hints", self.togglehelp, None, | |
(ord('h'),), 0, (0, 2, 7, 8)) | |
button.set_flags(gtk.CAN_FOCUS) | |
button.grab_focus() | |
#Make it all visible | |
for i in table: | |
i.show() | |
self.window.show() | |
def __init__(self, goal): | |
#Current board state as an integer | |
self.status = 0 | |
#Status for winning combination | |
self.goal = goal | |
# If true, give hints | |
self.helpflag = 0 | |
#If true, play game. Otherwise, set goal | |
self.playflag = 1 | |
self.setup_gui() | |
def main(): | |
#Status for winning combination | |
goal = len(sys.argv) <= 1 and 0757 or int(sys.argv[1], 0) | |
FlipGame(goal) | |
gtk.main() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment