0% found this document useful (0 votes)
13 views

Experimental Penguins Python

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Experimental Penguins Python

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 41

Directorio

*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

Contenido de archivos importantes del juego:


files/single_room.py
from autobahn.twisted.websocket import WebSocketServerProtocol, \
WebSocketServerFactory

from json import loads, dumps

#Todo
#*Remove duplicate code
#*Add broadcast function for multi-user messaging
#*Validate each packet

users = {}
rooms = ['main', 'right1']

class MyServerProtocol(WebSocketServerProtocol):

def onConnect(self, request):


print("Client connecting: {0}".format(request.peer))

def onOpen(self):
print("WebSocket connection open.")

def onMessage(self, payload, isBinary):


if isBinary:
print("Binary message received: {0}
bytes".format(len(payload)))
else:
data = loads(payload.decode('utf8'))

print('Received JSON: %s' % data)

_type = data['type']

try:
getattr(self, '_%s' % _type)(data, payload, isBinary)
except Exception as e:
print(e)
print('Not recognized')

def _join(self, data, payload, isBinary):

user_data = data['data']
username = user_data['username']
room = user_data['room']

if users.get(username):
print('Username taken. Disconnecting client..')
self.close()
return

if room not in rooms:


print('Invalid room. Disconnecting')
if getattr(self, 'room'):
try:
del users[self.room][username]
except Exception as e:
pass
self.close()
return

users[room][username] = (self, user_data)

for _, user in users.items():


if user[0] != self:
user[0].sendMessage(payload, isBinary)

self.username = username

print('User %s has joined!' % username)


print('User Count: %s' % len(users))
def _move(self, data, payload, isBinary):

username = data['username']
users[username][1]['direction'] = data['direction']
users[username][1]['shape']['x'] = data['sx2']
users[username][1]['shape']['y'] = data['sy2']

for _, user in users.items():


if user[0] != self:
user[0].sendMessage(payload, isBinary)

def _message(self, data, payload, isBinary):


for _, user in users.items():
if user[0] != self:
user[0].sendMessage(payload, isBinary)

def _users(self, data, payload, isBinary):


user_list = []

for _, user in users.items():


if user[0] != self:
user_list.append(user[1])

self.sendMessage(dumps({'users': user_list, 'type':


'users'}).encode(), isBinary)

def onClose(self, wasClean, code, reason):

print("WebSocket connection closed: {0}".format(reason))

del users[self.username]

print('User Count: %s' % len(users))

for _, user in users.items():


user[0].sendMessage(dumps({'type': 'leave', 'username':
self.username, 'room': self.room}).encode(), False)

if __name__ == '__main__':

import sys

from twisted.python import log


from twisted.internet import reactor

log.startLogging(sys.stdout)

factory = WebSocketServerFactory(u"ws://127.0.0.1:9000")
factory.protocol = MyServerProtocol
# factory.setProtocolOptions(maxConnections=2)

# note to self: if using putChild, the child must be bytes...

reactor.listenTCP(9000, factory)
reactor.run()

files/server.py
from autobahn.twisted.websocket import WebSocketServerProtocol, \
WebSocketServerFactory

from json import loads, dumps

#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 = {'rooms': {}}


rooms = ['main', 'right1']

for room in rooms:


users['rooms'][room] = {}

users['in_use'] = []

# MESSAGE LENGTH VERIFY


# ROOMVERIFY() UNCTOIN
# SOME ENCRYPTION MAYBE
# MSGPACK
#MONADS

class MyServerProtocol(WebSocketServerProtocol):

def onConnect(self, request):


self.username = None
print("Client connecting: {0}".format(request.peer))

def onOpen(self):
print("WebSocket connection open.")

def onMessage(self, payload, isBinary):


if isBinary:
print("Binary message received: {0}
bytes".format(len(payload)))
else:
data = loads(payload.decode('utf8'))

print('Received JSON: %s' % data)

_type = data['type']

try:
getattr(self, '_%s' % _type)(data, payload, isBinary)
except Exception as e:
print(e)
print('Not recognized')

def _username(self, data, payload, isBinary):

username = data['username']

print('User registering as %s' % username)

if not username or len(username) > 15:


self.sendMessage(dumps({'type': 'username', 'error':
'Username is invalid'}).encode(), isBinary)
self.transport.loseConnection()
return

if not username in users['in_use']:


users['in_use'].append(username)
self.username = username
self.sendMessage(dumps({'type': 'username', 'accepted':
True}).encode(), isBinary)
else:
self.sendMessage(dumps({'type': 'username', 'error':
'Username is taken'}).encode(), isBinary)
self.transport.loseConnection()

def _join(self, data, payload, isBinary):

user_data = data['data']
username = user_data['username']
room = user_data['room']

if room not in rooms:


print('Invalid room. Disconnecting')
self.close()
return
if getattr(self, 'username', None):
self.removeUserFromRooms()

users['rooms'][room][username] = (self, user_data)

for _, user in users['rooms'][room].items():


if user[0] != self:
user[0].sendMessage(payload, isBinary)

print('User %s has joined!' % username)

for room, _users in users['rooms'].items():


print('%s User Count: %s' % (room, len(_users)))

def _move(self, data, payload, isBinary):

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']

for _, user in users['rooms'][room].items():


if user[0] != self:
user[0].sendMessage(payload, isBinary)

def _message(self, data, payload, isBinary):

room = data['room']

for _, user in users['rooms'][room].items():


if user[0] != self:
user[0].sendMessage(payload, isBinary)

def _users(self, data, payload, isBinary):


user_list = []

room = data['room']

for _, user in users['rooms'][room].items():


if user[0] != self:
user_list.append(user[1])
self.sendMessage(dumps({'users': user_list, 'type':
'users'}).encode(), isBinary)

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)

def onClose(self, wasClean, code, reason):

print("WebSocket connection closed: {0}".format(reason))

#del users[self.username]

#if getattr(self, 'username', None):


self.removeUserFromRooms()

if getattr(self, 'username'):
users['in_use'].remove(self.username)

print('User Count: %s' % len(users['in_use']))

if __name__ == '__main__':

import sys

from twisted.python import log


from twisted.internet import reactor

log.startLogging(sys.stdout)

factory = WebSocketServerFactory(u"ws://127.0.0.1:9000")
factory.protocol = MyServerProtocol
# factory.setProtocolOptions(maxConnections=2)

# note to self: if using putChild, the child must be bytes...

reactor.listenTCP(9000, factory)
reactor.run()

*files/web/avatar.html*
<div id="stage">

<div id="ui" width="800" height="800">


<input type="text" id="msg" name="msg" maxlength="100">
<input type="image" src="send_btn.png" onclick="draw_text();"
id="send" name="send" class="btn"/>
<input type="image" src="disconnect_btn.png"
onclick="disconnect();" id="disconnect" name="disconnect"
class="btn"/>
<button onclick="to_right1();" id="right1" name="right1"
class="btn">Next room</button>
</div>

<div id="login" width="800" height="800">


<input type="text" id="username" name="username"/><br><br>
<button onclick="login();" id="login_btn"
name="login_btn">Connect</button>
</div>

<canvas id="game" width="800" height="800"></canvas>


<canvas id="background" width="800" height="800"></canvas>

</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;

//Background canvas Config


var background_canvas = document.getElementById('background',
{alpha: false});
var bg_ctx = background_canvas.getContext('2d');
var background = new Image();
background.src = 'background.png';
background.onload = function(){
bg_ctx.drawImage(background,0,0);
}

//Penguin Sprite Config


var image = new Image();
image.src = 'sprites_new.png';
image.height = 80;
image.width = 70;

//Default Penguin facing direction


var direction = 'south';

// Default room: main

//Message Logs
var logs = ['Welcome to Experimental Penguins HTML5!'];

//Penguin Sprite Frames


var penguin_frames = {'north' : {'start_frame': 8,
'frame_count': 8,
'end_frame': 3},
'north east' : {'start_frame': 16,
'frame_count': 8,
'end_frame': 4},
'north west' : {'start_frame': 24,
'frame_count': 8,
'end_frame': 5},
'east' : {'start_frame': 32,
'frame_count': 8,
'end_frame': 6},
'west' : {'start_frame': 40,
'frame_count': 8,
'end_frame': 7},
'south east' : {'start_frame': 48,
'frame_count': 8,
'end_frame': 0},
'south west' : {'start_frame': 56,
'frame_count': 8,
'end_frame': 1},
'south' : {'start_frame': 64,
'frame_count': 8,
'end_frame': 2},
'current_frame': 0,
'old_direction' : null }

//User related functions


function getUser(username) {
for(var i = 0; i < users.length; i++) {
if(users[i]['username'] == username) {
return users[i];
}
}
}

//Network handlers
var ws = null;

//Connect to the server


function connect() {

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']});

sendPacket({'type': 'username', 'username':


my_penguin['username']});
};

ws.onmessage = function (evt) {


console.log('Received: ' + evt.data);
var data = JSON.parse(evt.data);

//Packet Handlers

if(data['type'] == 'username') {
if(data['accepted']){

sendPacket({'type': 'join', 'data':


my_penguin});
sendPacket({'type': 'users', 'room':
my_penguin['room']});

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') {

var user = getUser(data['username']);


movePenguin(user, data['x2'], data['y2']);
}

};

ws.onclose = function() {
console.log('WebSocket Closed');
disconnect();
};

ws.onerror=function(event){
alert("Could not connect to server..");
}
}

//Disconnect from the server and redraw login screen


function disconnect() {

canvas.removeEventListener("click", onClick);
clearTimeout(draw_event);
clearTimeout(my_msg_timeout);
draw_event = null;
ws.close();
logs = ['Welcome to Experimental Penguins HTML5!'];

ctx.clearRect(0, 0, height, height);

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);
}

function getMousePos(canvas, evt) {


var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}

//Room Config
var users = []
var rooms = []

var bot = createPenguin('Bot');


bot['shape']['x'] = 300;
bot['shape']['y'] = 300

var my_penguin = null;

//Create a new penguin object


function createPenguin(username) {
var penguin = {};
penguin['username'] = username;
penguin['current_frame'] = 0;
penguin['shape'] = Object.assign({}, shape);
penguin['direction'] = 'south';
penguin['steps'] = 0;
penguin['dx'] = 0;
penguin['dy'] = 0;
penguin['room'] = 'main';
console.log('Penguin', penguin);

return penguin;
}

//Messaging functions

var my_msg_timeout;

function addMessage(user, message) {


logs.push(user['username'] + ': ' + message);

should_redraw();
user['message'] = message;

drawMessageBubble(user);

setTimeout(removeMessage, 3000, user);


}

//Clears a user's message


//This should be renamed to clearMessage
function removeMessage(user) {
user['message'] = null;
should_draw(true);
}

function draw_text(){
var text = document.getElementById('msg').value;

if(!text) {
console.log("Empty message box input!");
return;
}

if(text.length > 100) {


console.log("Message too long!");
return;
}

logs.push(my_penguin.username + ': ' + text);

clearTimeout(my_msg_timeout);

my_penguin['message'] = '';

should_redraw();

my_penguin['message'] = text;

drawMessageBubble(my_penguin);

sendPacket({'type': 'message', 'message': text, 'username':


my_penguin['username'], 'room': my_penguin['room']});
my_msg_timeout = setTimeout(removeMessage, 3000, 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();

//Message log function


function draw_logs() {

ctx.fillStyle = "#000000";

if(logs.length > 10) {


while(logs.length > 10) {
logs.shift();
}
}

for(var i = logs.length - 1; i >= 0; i--) {


ctx.fillText(logs[i], 40, 20 + i*10);

}
}

//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();
}

// clear message clear handlers on room switch

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 distance(x2, y2, x, y) {


return Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2))
}

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);
//}

if(force == true || users.some(hasSteps)){

ctx.clearRect(0, 0, height, height);


ctx.save();

users.forEach(user => {
if(user['steps'] > 0) {

user['shape'].x = user['shape'].x + user['dx'];


user['shape'].y = user['shape'].y + user['dy'];
user['steps']--;

ctx.drawImage(image, nextFrame(user) * sprite_width, 0,


sprite_width, sprite_height, user['shape'].x, user['shape'].y,
user['shape'].width, user['shape'].height);
} else {
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);

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(angle <= 0 + 20 && angle >= 0 - 20) {


return 'east';
}

if(angle <= 90 + 20 && angle >= 90 - 20) {


return 'south';
}

if(angle <= 180 + 20 && angle >= 180 - 20) {


return 'west';
}

if(angle <= 270 + 20 && angle >= 270 - 20) {


return 'north';
}

if(angle > 0 && angle < 90) {


return 'south east';
}

if(angle > 90 && angle < 180) {


return 'south west';
}

if(angle > 180 && angle < 270) {


return 'north west';
}
if(angle > 90 && angle < 360) {
return 'north east';
}

function movePenguin(user, x2, y2) {

var angle = Math.atan2(y2 - user['shape'].y - 40, x2 -


user['shape'].x - 25)

user['dx'] = speed * Math.cos(angle)


user['dy'] = speed * Math.sin(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'])
}

user['steps'] = Math.floor(distance(x2 - 35, y2 - 40,


user['shape'].x, user['shape'].y) / Math.sqrt(Math.pow(user['dx'],
2) + Math.pow(user['dy'], 2)));

console.log('Steps Left:', user['steps']);


console.log('dx', user['dx'], 'dy', user['dy']);

var theta_radians = Math.atan2(y2 - user['shape'].y - 40, x2 -


user['shape'].x - 25)

angle = theta_radians * (180/Math.PI);

if(angle < 0) {
angle += 360;
}
user['direction'] = getDirection(angle);

should_draw(false);

console.log(getDirection(angle));

function onClick(e) {

var real_xy = getMousePos(canvas, e);

//x2 = e.clientX;
//y2 = e.clientY;

x2 = real_xy.x;
y2 = real_xy.y;

//Remove this duplicate code by function


var angle = Math.atan2(y2 - my_penguin['shape'].y - 40, x2 -
my_penguin['shape'].x - 25)

my_penguin['dx'] = speed * Math.cos(angle)


my_penguin['dy'] = speed * Math.sin(angle)

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'])
}

console.log(speed * Math.cos(angle), speed * Math.sin(angle))

var steps = Math.floor(distance(x2 - 35, y2 - 40,


my_penguin['shape'].x, my_penguin['shape'].y) /
Math.sqrt(Math.pow(my_penguin['dx'], 2) + Math.pow(my_penguin['dy'],
2)));
//---end---Remove this duplicate code by function

var f_x2 = my_penguin['shape'].x + my_penguin['dx'] * steps


var f_y2 = my_penguin['shape'].y + my_penguin['dy'] * steps
var theta_radians = Math.atan2(y2 - my_penguin['shape'].y - 40,
x2 - my_penguin['shape'].x - 25)

angle = theta_radians * (180/Math.PI);

if(angle < 0) {
angle += 360;
}

sendPacket({'type': 'move', 'username': my_penguin['username'],


'x2' : x2, 'y2': y2, 'sx2': f_x2, 'sy2': f_y2, direction:
getDirection(angle), 'room': my_penguin['room']});
movePenguin(my_penguin, x2, y2);

//Clicking rate limited to half a second


canvas.removeEventListener("click", onClick);
setTimeout(function() { canvas.addEventListener("click",
onClick); }, 500);
return;

function drawLogin() {

document.getElementById('ui').style.display = 'none';
document.getElementById('background').style.display = 'none';
document.getElementById('login').style.display = '';

var text = 'Please enter a penguin name!'


var logo = new Image();

logo.src = 'logo.jpg';
logo.onload = function(){

ctx.drawImage(logo, canvas.width / 2 - logo.width / 2, 0);

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 id="ui" width="800" height="800">


<input type="text" id="msg" name="msg" maxlength="100">
<button onclick="draw_text();" id="send" name="send" class="btn">Send</button>
<button onclick="disconnect();" id="disconnect" name="disconnect"
class="btn">Disconnect</button>
<button onclick="to_right1();" id="right1" name="right1" class="btn">Next
room</button>
</div>

<div id="login" width="800" height="800">


<input type="text" id="username" name="username"/><br><br>
<button onclick="login();" id="login_btn" name="login_btn">Connect</button>
</div>
<canvas id="game" width="800" height="800"></canvas>
<canvas id="background" width="800" height="800"></canvas>

</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;

//Background canvas Config


var background_canvas = document.getElementById('background', {alpha: false});
var bg_ctx = background_canvas.getContext('2d');
var background = new Image();
background.src = 'background.png';
background.onload = function(){
bg_ctx.drawImage(background,0,0);
}

//Penguin Sprite Config


var image = new Image();
image.src = 'sprites_new.png';
image.height = 80;
image.width = 70;

//Default Penguin facing direction


var direction = 'south';

// Default room: main

//Message Logs
var logs = ['Welcome to Experimental Penguins HTML5!'];

//Penguin Sprite Frames


var penguin_frames = {'north' : {'start_frame': 8,
'frame_count': 8,
'end_frame': 3},
'north east' : {'start_frame': 16,
'frame_count': 8,
'end_frame': 4},
'north west' : {'start_frame': 24,
'frame_count': 8,
'end_frame': 5},
'east' : {'start_frame': 32,
'frame_count': 8,
'end_frame': 6},
'west' : {'start_frame': 40,
'frame_count': 8,
'end_frame': 7},
'south east' : {'start_frame': 48,
'frame_count': 8,
'end_frame': 0},
'south west' : {'start_frame': 56,
'frame_count': 8,
'end_frame': 1},
'south' : {'start_frame': 64,
'frame_count': 8,
'end_frame': 2},
'current_frame': 0,
'old_direction' : null }

//User related functions


function getUser(username) {
for(var i = 0; i < users.length; i++) {
if(users[i]['username'] == username) {
return users[i];
}
}
}

//Network handlers
var ws = null;

//Connect to the server


function connect() {

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']});

sendPacket({'type': 'username', 'username': my_penguin['username']});


};

ws.onmessage = function (evt) {


console.log('Received: ' + evt.data);
var data = JSON.parse(evt.data);

//Packet Handlers

if(data['type'] == 'username') {
if(data['accepted']){

sendPacket({'type': 'join', 'data': my_penguin});


sendPacket({'type': 'users', 'room': my_penguin['room']});

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') {

var user = getUser(data['username']);


movePenguin(user, data['x2'], data['y2']);
}

};

ws.onclose = function() {
console.log('WebSocket Closed');
disconnect();
};

ws.onerror=function(event){
alert("Could not connect to server..");
}
}

//Disconnect from the server and redraw login screen


function disconnect() {

canvas.removeEventListener("click", onClick);
clearTimeout(draw_event);
ws.close();

ctx.clearRect(0, 0, height, height);

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 = []

var bot = createPenguin('Bot');


bot['shape']['x'] = 300;
bot['shape']['y'] = 300

var my_penguin = null;

//Create a new penguin object


function createPenguin(username) {
var penguin = {};
penguin['username'] = username;
penguin['current_frame'] = 0;
penguin['shape'] = Object.assign({}, shape);
penguin['direction'] = 'south';
penguin['steps'] = 0;
penguin['dx'] = 0;
penguin['dy'] = 0;
penguin['room'] = 'main';
console.log('Penguin', penguin);

return penguin;
}

//Messaging functions

var my_msg_timeout;

function addMessage(user, message) {


logs.push(user['username'] + ': ' + message);

redraw();

user['message'] = message;

drawMessageBubble(user);

setTimeout(removeMessage, 3000, user);


}

//Clears a user's message


//This should be renamed to clearMessage
function removeMessage(user) {
user['message'] = null;
draw(true);
}

function draw_text(){
var text = document.getElementById('msg').value;

if(!text) {
console.log("Empty message box input!");
return;
}

if(text.length > 100) {


console.log("Message too long!");
return;
}

logs.push(my_penguin.username + ': ' + text);

clearTimeout(my_msg_timeout);

my_penguin['message'] = '';

redraw();

my_penguin['message'] = text;

drawMessageBubble(my_penguin);

sendPacket({'type': 'message', 'message': text, 'username': my_penguin['username'],


'room': my_penguin['room']});
my_msg_timeout = setTimeout(removeMessage, 3000, 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";

if(logs.length > 10) {


logs.shift();
}

for(var i = logs.length - 1; i >= 0; i--) {


ctx.fillText(logs[i], 40, 20 + i*10);

}
}

//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();
}

// clear message clear handlers on room switch


function nextFrame(user) {

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 distance(x2, y2, x, y) {


return Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2))
}

function hasSteps(user) {
if(user['steps'] > 0) {
return true;
}
}

function draw(force){

if(draw_event) {
clearTimeout(draw_event);
}

if(force == true || users.some(hasSteps)){

ctx.clearRect(0, 0, height, height);


ctx.save();

users.forEach(user => {
if(user['steps'] > 0) {

user['shape'].x = user['shape'].x + user['dx'];


user['shape'].y = user['shape'].y + user['dy'];
user['steps']--;

ctx.drawImage(image, nextFrame(user) * sprite_width, 0, sprite_width,


sprite_height, user['shape'].x, user['shape'].y, user['shape'].width,
user['shape'].height);
} else {
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);

if(user['message']) {
drawMessageBubble(user);
}

});

ctx.restore();
draw_logs();

draw_event = setTimeout(draw,50);

} else {
redraw();
}
}

function getDirection(angle) {

if(angle <= 0 + 20 && angle >= 0 - 20) {


return 'east';
}

if(angle <= 90 + 20 && angle >= 90 - 20) {


return 'south';
}

if(angle <= 180 + 20 && angle >= 180 - 20) {


return 'west';
}

if(angle <= 270 + 20 && angle >= 270 - 20) {


return 'north';
}

if(angle > 0 && angle < 90) {


return 'south east';
}

if(angle > 90 && angle < 180) {


return 'south west';
}

if(angle > 180 && angle < 270) {


return 'north west';
}
if(angle > 90 && angle < 360) {
return 'north east';
}

function movePenguin(user, x2, y2) {

var angle = Math.atan2(y2 - user['shape'].y - 40, x2 - user['shape'].x - 25)

user['dx'] = speed * Math.cos(angle)


user['dy'] = speed * Math.sin(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'])
}

user['steps'] = Math.floor(distance(x2 - 35, y2 - 40, user['shape'].x, user['shape'].y) /


Math.sqrt(Math.pow(user['dx'], 2) + Math.pow(user['dy'], 2)));

console.log('Steps Left:', user['steps']);


console.log('dx', user['dx'], 'dy', user['dy']);
var theta_radians = Math.atan2(y2 - user['shape'].y - 40, x2 - user['shape'].x - 25)

angle = theta_radians * (180/Math.PI);

if(angle < 0) {
angle += 360;
}

user['direction'] = getDirection(angle);

draw(false);

console.log(getDirection(angle));

function onClick(e) {

x2 = e.clientX;
y2 = e.clientY;

//Remove this duplicate code by function


var angle = Math.atan2(y2 - my_penguin['shape'].y - 40, x2 - my_penguin['shape'].x -
25)

my_penguin['dx'] = speed * Math.cos(angle)


my_penguin['dy'] = speed * Math.sin(angle)

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'])
}

console.log(speed * Math.cos(angle), speed * Math.sin(angle))

var steps = Math.floor(distance(x2 - 35, y2 - 40, my_penguin['shape'].x,


my_penguin['shape'].y) / Math.sqrt(Math.pow(my_penguin['dx'], 2) +
Math.pow(my_penguin['dy'], 2)));
//---end---Remove this duplicate code by function

var f_x2 = my_penguin['shape'].x + my_penguin['dx'] * steps


var f_y2 = my_penguin['shape'].y + my_penguin['dy'] * steps

var theta_radians = Math.atan2(y2 - my_penguin['shape'].y - 40, x2 -


my_penguin['shape'].x - 25)

angle = theta_radians * (180/Math.PI);

if(angle < 0) {
angle += 360;
}

sendPacket({'type': 'move', 'username': my_penguin['username'], 'x2' : x2, 'y2': y2,


'sx2': f_x2, 'sy2': f_y2, direction: getDirection(angle), 'room': my_penguin['room']});
movePenguin(my_penguin, x2, y2);

//Clicking rate limited to half a second


canvas.removeEventListener("click", onClick);
setTimeout(function() { canvas.addEventListener("click", onClick); }, 500);
return;

function drawLogin() {

document.getElementById('ui').style.display = 'none';
document.getElementById('background').style.display = 'none';
document.getElementById('login').style.display = '';

var text = 'Please enter a penguin name!'


var logo = new Image();

logo.src = 'logo.jpg';
logo.onload = function(){

ctx.drawImage(logo, canvas.width / 2 - logo.width / 2, 0);

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>

You might also like