Quiz Bowl
A real-time, simultaneous multi-player, online
quiz manager
Catalyst
package QuizBowl::Web; use Moose; use namespace::autoclean; use Catalyst::Runtime; use Catalyst qw/ -Debug ConfigLoader Static::Simple Session Session::State::Cookie Session::Store::DBIC Authentication /;
...
Sessions
__PACKAGE__->config(
name => 'QuizBowl::Web',
'Plugin::Session' => {
dbic_class => 'DB::Session',
expires => 60 * 60 * 24 * 7 * 2, # 2 weeks
id_field => 'session_id',
},
);
Authentication
"Plugin::Authentication":
default_realm: users
realms:
users:
credential:
class: Password
password_field: password
password_type: salted_hash
password_salt_len: 4
password_hash_type: SHA-1
store:
class: "DBIx::Class"
user_model: "DB::User"
sub login : Local {
my ( $self, $c ) = @_;
if (
$c->req->param('email')
&& $c->authenticate( ...
REST
package QuizBowl::Web::Controller::REST; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller::REST' } with 'Catalyst::TraitFor::Controller::DBIC::DoesPaging'; with 'Catalyst::TraitFor::Controller::DoesExtPaging'; __PACKAGE__->config( 'default' => 'application/json', 'map' => { 'text/html' => 'YAML::HTML', 'text/xml' => 'XML::Simple', 'application/xml' => 'XML::Simple', 'text/x-yaml' => 'YAML', 'application/json' => 'JSON',
...
REST Easy
my $users_rs = $c->model('DB::User')->search( {} );
my $paginated_rs = $self->page_and_sort( $c, $users_rs );
my $data_r = $self->ext_paginate( $paginated_rs, 'REST_data' );
Database
- Postgres
- DBIx::Class
package QuizBowl::Schema::Result::User; use Moose; use MooseX::NonMoose; extends 'QuizBowl::Schema::Result'; __PACKAGE__->table('user_account'); __PACKAGE__->add_columns( user_id => { data_type => 'serial', is_nullable => 0, },
...
Constraints/RElationships
__PACKAGE__->set_primary_key('user_id');
__PACKAGE__->add_unique_constraint( unique_email => ['email'], );
__PACKAGE__->has_many(
event_registrations => 'QuizBowl::Schema::Result::EventUser',
'user_id',
);
__PACKAGE__->many_to_many(
registered_events => 'event_registrations',
'player',
);
Database Tracing
Always remember!
- DBIC_TRACE=1
- DBIC_TRACE_PROFILE=console
my $email = Email::Simple->create(
header => [
To => $user->email_with_name,
From => 'system@example.com',
Subject => 'Quiz Bowl Password Help',
],
body => sprintf( 'Reset password %s', $uri->as_string() ),
);
sendmail($email);
Templates
[% USE date %]
...
<tbody> [% WHILE ( event = events_rs.next() ) %] <tr> <td>[% event.id %]</td> <td>[% event.name %]</td> <td>[% date.format(event.start_time, '%b %e %l:%S %p') %]</td> <td> [% IF c.user_exists %] <a href="[% c.uri_for_action('/event/run', [event.id]) %]" ...
Real-time Web
var socket = io.connect();
socket.on('roll call requested', function(){
$('.panel').hide();
$('#waiting_panel').show();
$('#round_number').text('Roll Call');
$('#question_level').text('Press Ready');
});
socket.on('user list updated', user_list_updated);
socket.on('round started', round_started);
socket.on('reconnecting', function(){
$.growl('Whoops, trying to reconnect...');
});
Perl Socket.IO
use QuizBowl::SocketIO; use PocketIO; use Plack::Builder; builder {
# ... mount '/socket.io' => builder { PocketIO->new( instance => QuizBowl::SocketIO->new(), method => 'run' ); }; };
PocketIO
package QuizBowl::SocketIO;
sub run {
my $self = shift;
return sub {
my $self = shift;
$self->on( 'submit answer' => \&submit_answer );
};
}
sub submit_answer {
my $self = shift;
my $answer = shift;
}
// in JavaScript:
$('#submit_button').click(function(){
QuizBowl.socket.emit('submit answer', $('#answer').val());
return false;
});
Rooms
- Host events simultaneously
# Join a room
$self->join($event_id);
# Send to everyone in room
$self->sockets->in($event_id)->emit(
'answer submitted',
{
user_id => $user_id,
event_question_id => $eq->id,
}
);
# Send to everyone in room _except_ me
$self->broadcast->to($event_id)->emit(
'growl',
sprintf( 'Round will close in %i seconds', $seconds )
);
Future
Quiz Bowl
By mcsnolte
Quiz Bowl
Overview of some of the tech in Quiz Bowl (2013.)
- 2,117