Experimental Penguins Python
Experimental Penguins Python
*dev/graphics*
background.xcf
penguin_sprites.xcf
*files/web*
README.md
avatar.html
avatar.html.back
background.png
disconnect_btn.png
logo.jpg
send_btn.png
sprites_new.png
*files/*
.gitignore
README.md
__init__.py
server.py
single_room.py
#Todo
#*Remove duplicate code
#*Add broadcast function for multi-user messaging
#*Validate each packet
users = {}
rooms = ['main', 'right1']
class MyServerProtocol(WebSocketServerProtocol):
def onOpen(self):
print("WebSocket connection open.")
_type = data['type']
try:
getattr(self, '_%s' % _type)(data, payload, isBinary)
except Exception as e:
print(e)
print('Not recognized')
user_data = data['data']
username = user_data['username']
room = user_data['room']
if users.get(username):
print('Username taken. Disconnecting client..')
self.close()
return
self.username = username
username = data['username']
users[username][1]['direction'] = data['direction']
users[username][1]['shape']['x'] = data['sx2']
users[username][1]['shape']['y'] = data['sy2']
del users[self.username]
if __name__ == '__main__':
import sys
log.startLogging(sys.stdout)
factory = WebSocketServerFactory(u"ws://127.0.0.1:9000")
factory.protocol = MyServerProtocol
# factory.setProtocolOptions(maxConnections=2)
reactor.listenTCP(9000, factory)
reactor.run()
files/server.py
from autobahn.twisted.websocket import WebSocketServerProtocol, \
WebSocketServerFactory
#Todo
#*Remove duplicate code
#*Add broadcast function for multi-user messaging
#*Validate each packet
#* Validate room size when a user is moving
#** This needs to map to both the client and server from some
config?
users['in_use'] = []
class MyServerProtocol(WebSocketServerProtocol):
def onOpen(self):
print("WebSocket connection open.")
_type = data['type']
try:
getattr(self, '_%s' % _type)(data, payload, isBinary)
except Exception as e:
print(e)
print('Not recognized')
username = data['username']
user_data = data['data']
username = user_data['username']
room = user_data['room']
username = data['username']
room = data['room']
users['rooms'][room][username][1]['direction'] =
data['direction']
users['rooms'][room][username][1]['shape']['x'] =
data['sx2']
users['rooms'][room][username][1]['shape']['y'] =
data['sy2']
room = data['room']
room = data['room']
def removeUserFromRooms(self):
for room, _users in users['rooms'].items():
if self.username in _users:
del users['rooms'][room][self.username]
for _, user in _users.items():
user[0].sendMessage(dumps({'type': 'leave',
'username': self.username, 'room': room}).encode(), False)
#del users[self.username]
if getattr(self, 'username'):
users['in_use'].remove(self.username)
if __name__ == '__main__':
import sys
log.startLogging(sys.stdout)
factory = WebSocketServerFactory(u"ws://127.0.0.1:9000")
factory.protocol = MyServerProtocol
# factory.setProtocolOptions(maxConnections=2)
reactor.listenTCP(9000, factory)
reactor.run()
*files/web/avatar.html*
<div id="stage">
</div>
<div id="footer">
<p>This site does not hold copyright for any files and was created
as an educational instance.</p>
</div>
<style>
#footer {
margin: auto;
text-align: center;
}
#stage {
width: 800px;
height: 800px;
position: relative;
border: 1px solid black;
margin: auto;
align: center;
}
canvas {
position: absolute;
}
#login_btn {
background-color: #FFBB33;
border: 1px solid black;
font-size: 20px;
font-weight: bold;
padding-left: 15px;
padding-right: 15px;
padding-top: 10px;
padding-bottom: 10px;
}
.btn {
background-color: #fff;
border: 1px solid black;
}
#ui {
z-index: 3;
left: 5px;
bottom: 5px;
position: absolute;
text-align: center;
}
#login {
z-index: 3;
position: absolute;
text-align: center;
left: 300px;
top: 400px;
}
#game {
z-index: 2;
}
#background {
z-index: 1;
}
input {
vertical-align: middle;
}
</style>
<script>
// Todo \\
//------------------------------------------
//------------------------------------------
//
//[done]: Load/Cache all images first, then connect websocket, then
get user list, then load room. Loading screen needed?
//[done]: Rooms
//*Multiple Servers
//*Sitting Sprite when S key clicked
//*Ability to sit in Snowcat
//[done]: Login screen
//*Server selection
//[done]: limit clicks per second for penguin movement
//[done]: minimum username length set to 2
//[done]:Character limit for username
//[done]: Character limit for message
// - above needed for server-side too
//*Igloos, I've been delayed audio
//*Wearable beta hat
//*Unused puffle sprite inserted into a room
//*Combine draw and redraw functions
//*Remove duplicate code in mouse click handler
//*Funny message obfuscation like ROT13 :^) or use XXTEA xD
//*Wrap more code in functions
//[done]: Penguin movement seems to speed up sometimes. draw_event
may not be cleared properly
// [done]: need to ceil negative values and floor positive values
// maybe make directions more smoother and rotate penguin with
mouse movement?
// maybe interface circle around player too
//*Shorten lines of code to the JS standard
//*Binary or msgpack protocol? Compression?
//-hitbox to go to room
//-join room animation?
//-wrap message text "bubble"
//[done]:server down error
//[done]: handle lost connection disconnect
//[done]: username taken
//max room size, room full, server full?
//------------------------------------------
//------------------------------------------
//Configurables
//Sprite Config
var sprite_height = 96;
var sprite_width = 96;
var shape = {x:100, y:100, width : 75, height : 80};
//Movement Config
var x2 = 100, y2 = 100;
var dx = 0, dy = 0;
var speed = 9;
//Canvas Config
var canvas = document.getElementById('game', {alpha: false});
var ctx = canvas.getContext('2d');
var height = 800;
canvas.width = canvas.height = height;
var draw_event = null;
//Message Logs
var logs = ['Welcome to Experimental Penguins HTML5!'];
//Network handlers
var ws = null;
ws = new WebSocket("ws://localhost:9000/");
ws.onopen = function() {
console.log('Web Socket Connected!');
// sendPacket({'type': 'join', 'data':
my_penguin});
// sendPacket({'type': 'users', 'room':
my_penguin['room']});
//Packet Handlers
if(data['type'] == 'username') {
if(data['accepted']){
should_draw(true);
document.getElementById('background').style.display = '';
document.getElementById('ui').style.display = '';
document.getElementById('login').style.display = 'none';
canvas.addEventListener("click", onClick);
} else {
if(data['error'].includes("taken")) {
alert("Username taken");
} else {
alert("Username too long! (15 characters
max)");
}
ws.close();
}
}
if(data['type'] == 'users') {
if(data['users'].length > 0) {
//users = [bot, my_penguin];
data['users'].forEach(user => {
users.push(user);
});
should_draw(true);
}
}
if(data['type'] == 'message') {
addMessage(getUser(data['username']),
data['message']);
}
if(data['type'] == 'join') {
users.push(data['data']);
should_draw(true);
}
if(data['type'] == 'leave') {
var user = getUser(data['username']);
users = users.filter(function(value, index,
arr){
return value != user;
});
should_draw(true);
}
if(data['type'] == 'move') {
};
ws.onclose = function() {
console.log('WebSocket Closed');
disconnect();
};
ws.onerror=function(event){
alert("Could not connect to server..");
}
}
canvas.removeEventListener("click", onClick);
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
draw_event = null;
ws.close();
logs = ['Welcome to Experimental Penguins HTML5!'];
document.getElementById('right1').onclick = to_right1;
document.getElementById('right1').innerHTML = 'Next Room';
drawLogin();
function sendPacket(data) {
var packed = JSON.stringify(data);
ws.send(packed);
console.log('Sent: ' + packed);
}
//Room Config
var users = []
var rooms = []
return penguin;
}
//Messaging functions
var my_msg_timeout;
should_redraw();
user['message'] = message;
drawMessageBubble(user);
function draw_text(){
var text = document.getElementById('msg').value;
if(!text) {
console.log("Empty message box input!");
return;
}
clearTimeout(my_msg_timeout);
my_penguin['message'] = '';
should_redraw();
my_penguin['message'] = text;
drawMessageBubble(my_penguin);
document.getElementById('msg').value = '';
}
function drawMessageBubble(user) {
ctx.save();
ctx.textAlign = 'center';
ctx.fillStyle = '#215c80';
ctx.textBaseline = 'middle';
ctx.fillText(user['message'], Math.floor(user['shape'].x +
(image.width / 2)), user['shape'].y - 10);
ctx.restore();
ctx.fillStyle = "#000000";
}
}
//Core functions
function clear() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
function redraw() {
ctx.clearRect(0,0,height,height);
ctx.save();
users.forEach(user => {
ctx.drawImage(image, animationDone(user['direction']) *
sprite_width, 0, sprite_width, sprite_height, user['shape'].x,
user['shape'].y, user['shape'].width, user['shape'].height);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(user['username'], Math.floor(user['shape'].x +
(image.width / 2)), user['shape'].y + user['shape'].height + 15);
ctx.strokeStyle = "black";
if(user['message']) {
drawMessageBubble(user);
}
});
ctx.restore();
draw_logs();
}
function nextFrame(user) {
if(user['old_direction'] != user['direction']) {
user['old_direction'] = user['direction'];
user['current_frame'] = -1;
}
if(user['current_frame'] ==
penguin_frames[user['direction']]['frame_count'] +
penguin_frames[user['direction']]['start_frame'] - 1) {
user['current_frame'] = -1;
}
if(user['current_frame'] == -1) {
user['current_frame'] =
penguin_frames[user['direction']]['start_frame'];
return user['current_frame'];
}
user['current_frame']++;
return user['current_frame'];
function animationDone(direction) {
return penguin_frames[direction]['end_frame'];
}
function hasSteps(user) {
if(user['steps'] > 0) {
return true;
}
}
function should_draw(force) {
if(draw_event != null) {
console.log("Draw event still running");
return;
} else {
clearTimeout(draw_event);
draw(force);
}
}
function should_redraw(force) {
if(draw_event != null) {
console.log("Draw event still running");
return;
} else {
clearTimeout(draw_event);
redraw();
}
}
function draw(force){
//if(draw_event != null) {
// return;
// clearTimeout(draw_event);
//}
users.forEach(user => {
if(user['steps'] > 0) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(user['username'], Math.floor(user['shape'].x +
(image.width / 2)), user['shape'].y + user['shape'].height + 15);
if(user['message']) {
drawMessageBubble(user);
}
});
ctx.restore();
draw_logs();
draw_event = setTimeout(draw,50);
} else {
clearTimeout(draw_event);
draw_event = null;
redraw();
}
}
function getDirection(angle) {
if(user['dx'] > 0) {
user['dx'] = Math.floor(user['dx'])
} else {
user['dx'] = Math.ceil(user['dx'])
}
if(user['dy'] > 0) {
user['dy'] = Math.floor(user['dy'])
} else {
user['dy'] = Math.ceil(user['dy'])
}
if(angle < 0) {
angle += 360;
}
user['direction'] = getDirection(angle);
should_draw(false);
console.log(getDirection(angle));
function onClick(e) {
//x2 = e.clientX;
//y2 = e.clientY;
x2 = real_xy.x;
y2 = real_xy.y;
if(my_penguin['dx'] > 0) {
my_penguin['dx'] = Math.floor(my_penguin['dx'])
} else {
my_penguin['dx'] = Math.ceil(my_penguin['dx'])
}
if(my_penguin['dy'] > 0) {
my_penguin['dy'] = Math.floor(my_penguin['dy'])
} else {
my_penguin['dy'] = Math.ceil(my_penguin['dy'])
}
if(angle < 0) {
angle += 360;
}
function drawLogin() {
document.getElementById('ui').style.display = 'none';
document.getElementById('background').style.display = 'none';
document.getElementById('login').style.display = '';
logo.src = 'logo.jpg';
logo.onload = function(){
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 20);
ctx.restore();
function login() {
var username = document.getElementById('username').value;
if(username.length > 1) {
my_penguin = createPenguin(username);
users = [bot, my_penguin]
connect();
} else {
alert("Username must be at least 2 letters long");
}
}
function to_right1() {
// move to right room
my_penguin['room'] = 'right1'
users = [bot, my_penguin];
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
draw_event=null;
if(my_penguin['steps'] > 0) {
for(i = 0; i < my_penguin['steps']; i++){
my_penguin['shape'].x = my_penguin['shape'].x +
my_penguin['dx'];
my_penguin['shape'].y = my_penguin['shape'].y +
my_penguin['dy'];
}
my_penguin['steps'] = 0;
}
my_penguin['message'] = '';
document.getElementById('right1').onclick = to_main;
document.getElementById('right1').innerHTML = 'Previous Room';
draw(true);
sendPacket({'type': 'join', 'data': my_penguin});
sendPacket({'type': 'users', 'room': my_penguin['room']});
function to_main() {
// move to main room
my_penguin['room'] = 'main'
users = [bot, my_penguin];
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
draw_event = null;
if(my_penguin['steps'] > 0) {
for(i = 0; i < my_penguin['steps']; i++){
my_penguin['shape'].x = my_penguin['shape'].x +
my_penguin['dx'];
my_penguin['shape'].y = my_penguin['shape'].y +
my_penguin['dy'];
}
my_penguin['steps'] = 0;
}
my_penguin['message'] = '';
document.getElementById('right1').onclick = to_right1;
document.getElementById('right1').innerHTML = 'Next Room';
draw(true);
sendPacket({'type': 'join', 'data': my_penguin});
sendPacket({'type': 'users', 'room': my_penguin['room']});
drawLogin();
</script>
*files/web/avatar.html.back*
<div id="stage">
</div>
<p>This site does not hold copyright for any files and was created as an educational
instance.</p>
<style>
#stage {
width: 800px;
height: 800px;
position: relative;
border: 1px solid black;
}
canvas {
position: absolute;
}
#login_btn {
background-color: #FFBB33;
border: 1px solid black;
font-size: 20px;
font-weight: bold;
padding-left: 15px;
padding-right: 15px;
padding-top: 10px;
padding-bottom: 10px;
}
.btn {
background-color: #fff;
border: 1px solid black;
}
#ui {
z-index: 3;
left: 5px;
bottom: 5px;
position: absolute;
}
#login {
z-index: 3;
position: absolute;
text-align: center;
left: 300px;
top: 400px;
}
#game {
z-index: 2;
}
#background {
z-index: 1;
}
</style>
<script>
// Todo \\
//------------------------------------------
//------------------------------------------
//
//[done]: Load/Cache all images first, then connect websocket, then get user list, then
load room. Loading screen needed?
//[done]: Rooms
//*Multiple Servers
//*Sitting Sprite when S key clicked
//*Ability to sit in Snowcat
//[done]: Login screen
//*Server selection
//[done]: limit clicks per second for penguin movement
//[done]: minimum username length set to 2
//*Character limit for username
//[done]: Character limit for message
// - above needed for server-side too
//*Igloos, I've been delayed audio
//*Wearable beta hat
//*Unused puffle sprite inserted into a room
//*Combine draw and redraw functions
//*Remove duplicate code in mouse click handler
//*Funny message obfuscation like ROT13 :^) or use XXTEA xD
//*Wrap more code in functions
//[done]: Penguin movement seems to speed up sometimes. draw_event may not be
cleared properly
// [done]: need to ceil negative values and floor positive values
// maybe make directions more smoother and rotate penguin with mouse movement?
// maybe interface circle around player too
//*Shorten lines of code to the JS standard
//*Binary or msgpack protocol? Compression?
//-hitbox to go to room
//-join room animation?
//-wrap message text "bubble"
//[done]:server down error
//[done]: handle lost connection disconnect
//[done]: username taken
//max room size, room full, server full?
//------------------------------------------
//------------------------------------------
//Configurables
//Sprite Config
var sprite_height = 96;
var sprite_width = 96;
var shape = {x:100, y:100, width : 70, height : 80};
//Movement Config
var x2 = 100, y2 = 100;
var dx = 0, dy = 0;
var speed = 9;
//Canvas Config
var canvas = document.getElementById('game', {alpha: false});
var ctx = canvas.getContext('2d');
var height = 800;
canvas.width = canvas.height = height;
var draw_event = null;
//Message Logs
var logs = ['Welcome to Experimental Penguins HTML5!'];
//Network handlers
var ws = null;
ws = new WebSocket("ws://localhost:9000/");
ws.onopen = function() {
console.log('Web Socket Connected!');
// sendPacket({'type': 'join', 'data': my_penguin});
// sendPacket({'type': 'users', 'room': my_penguin['room']});
//Packet Handlers
if(data['type'] == 'username') {
if(data['accepted']){
draw(true);
document.getElementById('background').style.display = '';
document.getElementById('ui').style.display = '';
document.getElementById('login').style.display = 'none';
canvas.addEventListener("click", onClick);
} else {
alert("Username taken");
ws.close();
}
}
if(data['type'] == 'users') {
if(data['users'].length > 0) {
//users = [bot, my_penguin];
data['users'].forEach(user => {
users.push(user);
});
draw(true);
}
}
if(data['type'] == 'message') {
addMessage(getUser(data['username']), data['message']);
}
if(data['type'] == 'join') {
users.push(data['data']);
draw(true);
}
if(data['type'] == 'leave') {
var user = getUser(data['username']);
users = users.filter(function(value, index, arr){
return value != user;
});
draw(true);
}
if(data['type'] == 'move') {
};
ws.onclose = function() {
console.log('WebSocket Closed');
disconnect();
};
ws.onerror=function(event){
alert("Could not connect to server..");
}
}
canvas.removeEventListener("click", onClick);
clearTimeout(draw_event);
ws.close();
document.getElementById('right1').onclick = to_right1;
document.getElementById('right1').innerHTML = 'Next Room';
drawLogin();
function sendPacket(data) {
var packed = JSON.stringify(data);
ws.send(packed);
console.log('Sent: ' + packed);
}
//Room Config
var users = []
var rooms = []
return penguin;
}
//Messaging functions
var my_msg_timeout;
redraw();
user['message'] = message;
drawMessageBubble(user);
function draw_text(){
var text = document.getElementById('msg').value;
if(!text) {
console.log("Empty message box input!");
return;
}
clearTimeout(my_msg_timeout);
my_penguin['message'] = '';
redraw();
my_penguin['message'] = text;
drawMessageBubble(my_penguin);
document.getElementById('msg').value = '';
}
function drawMessageBubble(user) {
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(user['message'], Math.floor(user['shape'].x + (image.width / 2)),
user['shape'].y - 10);
ctx.restore();
}
//Message log function
function draw_logs() {
ctx.fillStyle = "#000000";
}
}
//Core functions
function clear() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
function redraw() {
ctx.clearRect(0,0,height,height);
ctx.save();
users.forEach(user => {
ctx.drawImage(image, animationDone(user['direction']) * sprite_width, 0,
sprite_width, sprite_height, user['shape'].x, user['shape'].y, user['shape'].width,
user['shape'].height);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(user['username'], Math.floor(user['shape'].x + (image.width / 2)),
user['shape'].y + user['shape'].height + 15);
ctx.strokeStyle = "black";
if(user['message']) {
drawMessageBubble(user);
}
});
ctx.restore();
draw_logs();
}
if(user['old_direction'] != user['direction']) {
user['old_direction'] = user['direction'];
user['current_frame'] = 0;
}
if(user['current_frame'] == penguin_frames[user['direction']]['frame_count'] +
penguin_frames[user['direction']]['start_frame'] - 1) {
user['current_frame'] = 0;
}
if(user['current_frame'] == 0) {
user['current_frame'] = penguin_frames[user['direction']]['start_frame'];
return user['current_frame'];
}
user['current_frame']++;
return user['current_frame'];
function animationDone(direction) {
return penguin_frames[direction]['end_frame'];
}
function hasSteps(user) {
if(user['steps'] > 0) {
return true;
}
}
function draw(force){
if(draw_event) {
clearTimeout(draw_event);
}
users.forEach(user => {
if(user['steps'] > 0) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(user['username'], Math.floor(user['shape'].x + (image.width / 2)),
user['shape'].y + user['shape'].height + 15);
if(user['message']) {
drawMessageBubble(user);
}
});
ctx.restore();
draw_logs();
draw_event = setTimeout(draw,50);
} else {
redraw();
}
}
function getDirection(angle) {
if(user['dx'] > 0) {
user['dx'] = Math.floor(user['dx'])
} else {
user['dx'] = Math.ceil(user['dx'])
}
if(user['dy'] > 0) {
user['dy'] = Math.floor(user['dy'])
} else {
user['dy'] = Math.ceil(user['dy'])
}
if(angle < 0) {
angle += 360;
}
user['direction'] = getDirection(angle);
draw(false);
console.log(getDirection(angle));
function onClick(e) {
x2 = e.clientX;
y2 = e.clientY;
if(my_penguin['dx'] > 0) {
my_penguin['dx'] = Math.floor(my_penguin['dx'])
} else {
my_penguin['dx'] = Math.ceil(my_penguin['dx'])
}
if(my_penguin['dy'] > 0) {
my_penguin['dy'] = Math.floor(my_penguin['dy'])
} else {
my_penguin['dy'] = Math.ceil(my_penguin['dy'])
}
if(angle < 0) {
angle += 360;
}
function drawLogin() {
document.getElementById('ui').style.display = 'none';
document.getElementById('background').style.display = 'none';
document.getElementById('login').style.display = '';
logo.src = 'logo.jpg';
logo.onload = function(){
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 20);
ctx.restore();
function login() {
var username = document.getElementById('username').value;
if(username.length > 1) {
my_penguin = createPenguin(username);
users = [bot, my_penguin]
connect();
} else {
alert("Username must be at least 2 letters long");
}
}
function to_right1() {
// move to right room
my_penguin['room'] = 'right1'
users = [bot, my_penguin];
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
if(my_penguin['steps'] > 0) {
for(i = 0; i < my_penguin['steps']; i++){
my_penguin['shape'].x = my_penguin['shape'].x + my_penguin['dx'];
my_penguin['shape'].y = my_penguin['shape'].y + my_penguin['dy'];
}
my_penguin['steps'] = 0;
}
my_penguin['message'] = '';
document.getElementById('right1').onclick = to_main;
document.getElementById('right1').innerHTML = 'Previous Room';
draw(true);
sendPacket({'type': 'join', 'data': my_penguin});
sendPacket({'type': 'users', 'room': my_penguin['room']});
function to_main() {
// move to main room
my_penguin['room'] = 'main'
users = [bot, my_penguin];
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
if(my_penguin['steps'] > 0) {
for(i = 0; i < my_penguin['steps']; i++){
my_penguin['shape'].x = my_penguin['shape'].x + my_penguin['dx'];
my_penguin['shape'].y = my_penguin['shape'].y + my_penguin['dy'];
}
my_penguin['steps'] = 0;
}
my_penguin['message'] = '';
document.getElementById('right1').onclick = to_right1;
document.getElementById('right1').innerHTML = 'Next Room';
draw(true);
sendPacket({'type': 'join', 'data': my_penguin});
sendPacket({'type': 'users', 'room': my_penguin['room']});
drawLogin();
</script>