Skip to content

Commit

Permalink
Added TicTacToe to canvas (aimacode#174)
Browse files Browse the repository at this point in the history
* added kernel test notebook

* modified kernel.ipynb

* Added canvas support

* Added mouseclick to canvas

* add TicTacToe to canvas

* removed import statement in games.py

* corrected HTML tags

* Added comments and doctring

* Added methods for drawing with normalized units

* Added text support

* Fixed int type during normalization
  • Loading branch information
Chipe1 authored and norvig committed Apr 8, 2016
1 parent ed41795 commit 68a6600
Show file tree
Hide file tree
Showing 4 changed files with 641 additions and 53 deletions.
130 changes: 130 additions & 0 deletions canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
JavaScript functions that are executed by running the corresponding methods of a Canvas object
Donot use these functions by making a js file. Instead use the python Canvas class.
See canvas.py for help on how to use the Canvas class to draw on the HTML Canvas
*/

//Manages the output of code executed in IPython kernel
function output_callback(out, block){
console.log(out);
script = out.content.data['text/html'];
console.log(script);
script = script.substr(8, script.length - 17);
console.log(script);
eval(script)
}

//Handles mouse click by calling mouse_click of Canvas object with the co-ordinates as arguments
function click_callback(element, event, varname){
var rect = element.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
var kernel = IPython.notebook.kernel;
var exec_str = varname + ".mouse_click(" + String(x) + ", " + String(y) + ")";
console.log(exec_str);
kernel.execute(exec_str,{'iopub': {'output': output_callback}}, {silent: false});
}

function rgbToHex(r,g,b){
var hexValue=(r<<16) + (g<<8) + (b<<0);
var hexString=hexValue.toString(16);
hexString ='#' + Array(7-hexString.length).join('0') + hexString; //Add 0 padding
return hexString;
}

function toRad(x){
return x*Math.PI/180;
}

//Canvas class to store variables
function Canvas(id){
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext("2d");
this.WIDTH = this.canvas.width;
this.HEIGHT = this.canvas.height;
this.MOUSE = {x:0,y:0};
}

//Sets the fill color with which shapes are filled
Canvas.prototype.fill = function(r, g, b){
this.ctx.fillStyle = rgbToHex(r,g,b);
}

//Set the stroke color
Canvas.prototype.stroke = function(r, g, b){
this.ctx.strokeStyle = rgbToHex(r,g,b);
}

//Set width of the lines/strokes
Canvas.prototype.strokeWidth = function(w){
this.ctx.lineWidth = w;
}

//Draw a rectangle with top left at (x,y) with 'w' width and 'h' height
Canvas.prototype.rect = function(x, y, w, h){
this.ctx.fillRect(x,y,w,h);
}

//Draw a line with (x1, y1) and (x2, y2) as end points
Canvas.prototype.line = function(x1, y1, x2, y2){
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
}

//Draw an arc with (x, y) as centre, 'r' as radius from angles start to stop
Canvas.prototype.arc = function(x, y, r, start, stop){
this.ctx.beginPath();
this.ctx.arc(x, y, r, toRad(start), toRad(stop));
this.ctx.stroke();
}

//Clear the HTML canvas
Canvas.prototype.clear = function(){
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
}

//Change font, size and style
Canvas.prototype.font = function(font_str){
this.ctx.font = font_str;
}

//Draws "filled" text on the canvas
Canvas.prototype.fill_text = function(text, x, y){
this.ctx.fillText(text, x, y);
}

//Write text on the canvas
Canvas.prototype.stroke_text = function(text, x, y){
this.ctx.strokeText(text, x, y);
}


//Test if the canvas functions are working
Canvas.prototype.test_run = function(){
var dbg = false;
if(dbg)
alert("1");
this.clear();
if(dbg)
alert("2");
this.fill(0, 200, 0);
if(dbg)
alert("3");
this.rect(this.MOUSE.x, this.MOUSE.y, 100, 200);
if(dbg)
alert("4");
this.stroke(0, 0, 50);
if(dbg)
alert("5");
this.line(0, 0, 100, 100);
if(dbg)
alert("6");
this.stroke(200, 200, 200);
if(dbg)
alert("7");
this.arc(200, 100, 50, 0, 360);
if(dbg)
alert("8");
}
122 changes: 122 additions & 0 deletions canvas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from IPython.display import HTML, display, clear_output

_canvas = """
<script type="text/javascript" src="./canvas.js"></script>
<div>
<canvas id="{0}" width="{1}" height="{2}" style="background:rgba(158, 167, 184, 0.2);" onclick='click_callback(this, event, "{3}")'></canvas>
</div>
<script> var {0}_canvas_object = new Canvas("{0}");</script>
"""

class Canvas:
"""Inherit from this class to manage the HTML canvas element in jupyter notebooks.
To create an object of this class any_name_xyz = Canvas("any_name_xyz")
The first argument given must be the name of the object being create
IPython must be able to refernce the variable name that is being passed
"""

def __init__(self, varname, id=None, width=800, height=600):
""""""
self.name = varname
self.id = id or varname
self.width = width
self.height = height
self.html = _canvas.format(self.id, self.width, self.height, self.name)
self.exec_list = []
display(HTML(self.html))

def mouse_click(self, x, y):
"Override this method to handle mouse click at position (x, y)"
raise NotImplementedError

def mouse_move(self, x, y):
raise NotImplementedError

def exec(self, exec_str):
"Stores the command to be exectued to a list which is used later during update()"
if not isinstance(exec_str, str):
print("Invalid execution argument:",exec_str)
self.alert("Recieved invalid execution command format")
prefix = "{0}_canvas_object.".format(self.id)
self.exec_list.append(prefix + exec_str + ';')

def fill(self, r, g, b):
"Changes the fill color to a color in rgb format"
self.exec("fill({0}, {1}, {2})".format(r, g, b))

def stroke(self, r, g, b):
"Changes the colors of line/strokes to rgb"
self.exec("stroke({0}, {1}, {2})".format(r, g, b))

def strokeWidth(self, w):
"Changes the width of lines/strokes to 'w' pixels"
self.exec("strokeWidth({0})".format(w))

def rect(self, x, y, w, h):
"Draw a rectangle with 'w' width, 'h' height and (x, y) as the top-left corner"
self.exec("rect({0}, {1}, {2}, {3})".format(x, y, w, h))

def rect_n(self, xn, yn, wn, hn):
"Similar to rect(), but the dimensions are normalized to fall between 0 and 1"
x = round(xn * self.width)
y = round(yn * self.height)
w = round(wn * self.width)
h = round(hn * self.height)
self.rect(x, y, w, h)

def line(self, x1, y1, x2, y2):
"Draw a line from (x1, y1) to (x, y2)"
self.exec("line({0}, {1}, {2}, {3})".format(x1, y1, x2, y2))

def line_n(self, x1n, y1n, x2n, y2n):
"Similar to line(), but the dimensions are normalized to fall between 0 and 1"
x1 = round(x1n * self.width)
y1 = round(y1n * self.height)
x2 = round(x2n * self.width)
y2 = round(y2n * self.height)
self.line(x1, y1, x2, y2)

def arc(self, x, y, r, start, stop):
"Draw an arc with (x, y) as centre, 'r' as radius from angles 'start' to 'stop'"
self.exec("arc({0}, {1}, {2}, {3}, {4})".format(x, y, r, start, stop))

def arc_n(self, xn ,yn, rn, start, stop):
"""Similar to arc(), but the dimensions are normalized to fall between 0 and 1
The normalizing factor for radius is selected between width and height by seeing which is smaller
"""
x = round(xn * self.width)
y = round(yn * self.height)
r = round(rn * min(self.width, self.height))
self.arc(x, y, r, start, stop)

def clear(self):
"Clear the HTML canvas"
self.exec("clear()")

def font(self, font):
"Changes the font of text"
self.exec('font("{0}")'.format(font))

def text(self, txt, x, y, fill = True):
"Display a text at (x, y)"
if fill:
self.exec('fill_text("{0}", {1}, {2})'.format(txt, x, y))
else:
self.exec('stroke_text("{0}", {1}, {2})'.format(txt, x, y))

def text_n(self, txt, xn, yn, fill = True):
"Similar to text(), but with normalized coordinates"
x = round(xn * self.width)
y = round(yn * self.height)
self.text(text, x, y, fill)

def alert(self, message):
"Immediately display an alert"
display(HTML('<script>alert("{0}")</script>'.format(message)))

def update(self):
"Execute the JS code to execute the commands queued by exec()"
exec_code = "<script>\n"+'\n'.join(self.exec_list)+"\n</script>"
self.exec_list = []
display(HTML(exec_code))
Loading

0 comments on commit 68a6600

Please sign in to comment.