Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Created April 16, 2017 15:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PM2Ring/3a016965d9f0d8cbfb5addb4219cc725 to your computer and use it in GitHub Desktop.
Save PM2Ring/3a016965d9f0d8cbfb5addb4219cc725 to your computer and use it in GitHub Desktop.
Flip - an infuriating XOR-based game for Python 2, using GTK2+
#!/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