So a while back I had gotten 3-in-1 Sodoku Garden on my cell phone because I like Sodoku even though I’m not that great at it. I figured, 3 games for the price of 1, wicked cool. Turns out one of the 3 games would later steal my heart. It went by the name “Tenpenki” and I couldn’t find anything on the interwebs about it that didn’t include the game on my cell phone. I thought to myself, “self, this would be a great project to learn PyGTK and you’d be rewarded for your efforts with a version of Tenpenki for the computer”. And it was. Funny enough though, once I got into coding my version of the puzzle game, I did some further digging on the game and I came across the Wikipedia page that completely summed up the alleged “Tenpenki”. It was called a Nonogram and it went by many names, none of which included “Tenpenki”. At this point I figured, no reason to stop just my Nonogram project just because Simon Tatham’s Portable Puzzle Collection has one. And so I present to you my Nonogram Demo. It’s extremely hard due to the random puzzle generation with no regard for skill level. I also opted to use check boxes either checked or unchecked, this just adds to the confusion as you can’t mark off any cells are “definitely blank”. Expect a better version in the future.

#!/usr/bin/env python

# Nonogram Demo
#
# This script generates a 10x10 pseudo-randomly generated Nonogram puzzle
# with hints.  This is my first app in GTK since I dabbled with it when
# PHP-GTK was first released, and it's my first application in PyGTK since
# I started dabbling with Python not to long ago.
#
# If you're wondering what a Nanogram is, go here:
# http://en.wikipedia.org/wiki/Nonogram
#
# These types of puzzles are also known as Paint by Numbers, Picross, Pixel
# Puzzles and the name that I discovered the game under, Tenpenki.
#
# @author Josh Sherman
# @link   http://joshtronic.com

import pygtk
pygtk.require('2.0')
import gtk
import random

player_on = 0

class Nonogram:
    # Callback function that checks if the puzzle has been completed successfully
    def callback(self, widget, correct_cell_value, total_on):
        # TODO: I know globals are bad, but when returning from the callback had unexpected results (for another day!)
        global player_on

        player_cell_value = (0, 1)[widget.get_active()]

        if player_cell_value == correct_cell_value:
            # Increment the player total when they check a cell
            if player_cell_value == 1:
                player_on += 1
        else:
            # Decrement the player total when they uncheck a cell
            if correct_cell_value == 1 and player_cell_value == 0:
                player_on -= 1

        # Display a short message upon completion of the puzzle
        if player_on == total_on:
            dialog = gtk.MessageDialog(
                parent         = self.window,
                flags          = gtk.DIALOG_DESTROY_WITH_PARENT,
                type           = gtk.MESSAGE_INFO,
                buttons        = gtk.BUTTONS_OK,
                message_format = "You have successfully completed the Nonogram\n\nThis demo will terminate when you click 'OK'\n\nCheers!\n"
            )

            dialog.set_title('Congratulations')
            dialog.connect('response', lambda dialog, response: gtk.main_quit())

            dialog.show()

        return player_on

    # Quits the program
    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    # Initial logic to draw all the GUI widgets
    def __init__(self, grid):

        total_on          = 0
        player_on         = 0
        current_count     = 0
        current_col_count = []
        current_row_count = []
        all_col_counts    = []
        all_row_counts    = []

        # Loops through all the cells in the grid
        for i in range(10):
            current_count = 0

            # Loops through all the cells in the row
            for j in grid[i]:

                # Increment the counts
                if j == 1:
                    current_count += 1
                    total_on      += 1
                # Add the total to the list and reset
                else:
                    if current_count != 0:
                        current_row_count.append(current_count)

                    current_count = 0

            # This catches the count if the last cell isn't 0
            if current_count != 0:
                current_row_count.append(current_count)

            all_row_counts.append(current_row_count)
            current_row_count = []
            current_count     = 0

            # Loops through all the cells in the column
            for j in range(10):
                # Increment the counts
                if grid[j][i] == 1:
                    current_count += 1
                # Add the total to the list and reset
                else:
                    if current_count != 0:
                        current_col_count.append(current_count)

                    current_count = 0

            # This catches the count if the last cell isn't 0
            if current_count != 0:
                current_col_count.append(current_count)

            all_col_counts.append(current_col_count)
            current_col_count = []

        # Creates a new window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

        # Sets the window title
        self.window.set_title("Nonogram Demo")

        # Sets a handler for delete_event that immediately exits GTK
        self.window.connect("delete_event", self.delete_event)

        # Sets the border width of the window
        self.window.set_border_width(0)

        # Adds our main layout box
        vbox = gtk.VBox(True, 0)
        self.window.add(vbox)

        # Determines how much padding is necessary on the column hints
        max_col_hints = 0
        for i in range(10):
            current_hints = len(all_col_counts[i])

            if current_hints > max_col_hints:
                max_col_hints = current_hints

        # Pads the column hint lists
        for i in range(10):
            padding = max_col_hints - len(all_col_counts[i])

            for j in range(padding):
                all_col_counts[i].insert(0, '')

        # Determines how much padding is necessary on the row hints
        max_row_hints = 0
        for i in range(10):
            current_hints = len(all_row_counts[i])

            if current_hints > max_row_hints:
                max_row_hints = current_hints

        # Pads the row hint lists
        for i in range(10):
            padding = max_row_hints - len(all_row_counts[i])

            for j in range(padding):
                all_row_counts[i].insert(0, '')

        # Generates the column hints
        for i in range(max_col_hints):
            hbox = gtk.HBox(True, 0)
            vbox.add(hbox)

            # Adds padding to offset the row hints
            for j in range(max_row_hints):
                label = gtk.Label('')
                label.show()
                hbox.add(label)

            # Adds the hints
            for j in range(10):
                label = gtk.Label(all_col_counts[j][i])
                label.show()
                hbox.add(label)

            hbox.show()

        # Generates the playing area (with row hints)
        for i in range(10):
            hbox = gtk.HBox(True, 0)
            vbox.add(hbox)

            # Adds the row hints
            for count in all_row_counts[i]:
                label = gtk.Label(count)
                label.show()
                hbox.add(label)

            # Generates the grid of checkboxes
            for j in range(10):
                button = gtk.CheckButton()
                button.connect('toggled', self.callback, grid[i][j], total_on)
                hbox.pack_start(button, True, True, 2)
                button.show()

            hbox.show()

        vbox.show()

        self.window.show()

def main():
    gtk.main()
    return 0

if __name__ == "__main__":

    # Generates the 10x10 grid and populates the values
    Nonogram([[random.randint(0, 1) for column in range(10)] for row in range(10)])
    main()




Did you enjoy this post?

Cool if I slip into your inbox with more?
Full posts, 1-2 times per week: