#!/usr/bin/env python3
#
#	life --- Life game --
#
#	An implement of algorithm introduced by Mr.Ikuo Takeuchi
#	in article 'Get difference, get profit' contained in
#	'Programming seminar' (Kyoritu publishing, ISBN 978-4-320-02246-1)
#
# Copyright (c) 2020, Koh-ichi Ito All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright notice, 
#   this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, 
#   this list of conditions and the following disclaimer in the documentation 
#   and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the names of its contributors 
#   may be used to endorse or promote products derived from this software 
#   without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

PAUSE = 0.5

import curses
import os
import random
import signal
import sys
import time

def fresh_space(north_south, east_west):
	rv = []
	for lat in range(0, north_south + 2):
		rv.append( [] )
		for lng in range(0, east_west + 2):
			rv[lat].append(0)
	return rv

def init_alive(n, north_south, east_west):
	random.seed()
	rv = []
	space = fresh_space(north_south, east_west)
	while 0 < n:
		lat = random.randint(1, north_south)
		lng = random.randint(1, east_west)
		if not space[lat][lng]:
			rv.append( {'latitude': lat, 'longitude': lng} )
			space[lat][lng] = True
			n -= 1
	return rv

def populate(space, cell):
	#
	# Note that north-most and south-most row, east-most and west-most
	# column are padding to avoid overrun.
	# Mr.Takeuchi named such usage as 'victim'.
	#
	for lat in range(cell['latitude' ] - 1, cell['latitude'] + 2):
		for lng in range(cell['longitude'] - 1, cell['longitude'] + 2):
			space[lat][lng] += 1			# Hi, neighbors!
	space[cell['latitude'] ][cell['longitude'] ] -= 1	# compensation
	space[cell['latitude'] ][cell['longitude'] ] |= 0x10	# I am here!

def transition(win, space, north_south, east_west):
	rv = []
	# Skip 'victim' rows and columns.
	for lat in range(1, north_south + 1):
		for lng in range(1, east_west + 1):
			#
			# Less significant 4 bits holds #neighbors.
			# The 5th bit(0x10) is flag of current existance.
			#
			if (space[lat][lng] == 0x03	# new born
			 or space[lat][lng] == 0x12	# survive
			 or space[lat][lng] == 0x13):	# survive
				rv.append( {'latitude': lat, 'longitude': lng} )
				try:
					win.addch(lat - 1, lng - 1, '*')
				except:
					#
					# Reference of window.addch() says:
					#
					# Attempting to write to the lower
					# right corner of a window, subwindow,
					# or pad will cause an exception to be
					# raised after the character is printed.
					#
					if (lat == north_south
					and lng == east_west):
						pass
					else:
						curses.endwin()
						raise
			else:
				try:
					win.addch(lat - 1, lng - 1, ' ')
				except:
					if (lat == north_south
					and lng == east_west):
						pass
					else:
						curses.endwin()
						raise
	return rv

def abort(dummy0, dummy1):
	curses.endwin()
	sys.exit(1)

def usage():
	sys.stderr.write('Usage: %s <#seed>\n' % MyName)
	sys.exit(os.EX_USAGE)

MyName = os.path.basename(sys.argv[0] )
if len(sys.argv) != 2:
	usage()
	# never reach
try:
	n = int(sys.argv[1] )
	if n < 1:
		sys.stderr.write(
			'%s: <#seed> must be positive integer.\n' % (MyName)
		)
		sys.exit(os.EX_DATAERR)
		# never reach
except TypeError:
	usage()
	# never reach
win = curses.initscr()
signal.signal(signal.SIGINT, abort)
(north_south, east_west) = win.getmaxyx()
if north_south * east_west <= n:
	curses.endwin()
	sys.stderr.write(
		  '%s: %d as <#seed> too much. must be smaller than %d\n'
		% (MyName, n, north_south * east_west)
	)
	sys.exit(os.EX_DATAERR)
alive = init_alive(n, north_south, east_west)
while True:
	space = fresh_space(north_south, east_west)
	for cell in alive:
		populate(space, cell)
	alive = transition(win, space, north_south, east_west)
	win.refresh()
	if len(alive) < 1:
		break
	time.sleep(PAUSE)
curses.endwin()
print('Creature has extinct.')
sys.exit(0)
