Skip to content

Instantly share code, notes, and snippets.

@nihen
Created November 3, 2012 08:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nihen/4006644 to your computer and use it in GitHub Desktop.
Save nihen/4006644 to your computer and use it in GitHub Desktop.
isucon2
use 5.14.0;
use Plack::Request;
use IO::Handle;
use Encode;
use List::Util qw/sum/;
use POSIX qw/strftime/;
use JSON::XS;
my $json_encoder = JSON::XS->new->latin1;
my $json_decoder = JSON::XS->new->utf8;
my $header = ['content-type' => 'text/html'];
my $variation_count = 64 * 64;
my $artists = [qw/NHN48 はだいろクローバーZ/];
my $tickets = [
[
1, # artist_id
'西武ドームライブ',
[$variation_count, $variation_count],
],
[
1, # artist_id
'東京ドームライブ',
[$variation_count, $variation_count],
],
[
2, # artist_id
'さいたまスーパーアリーナライブ',
[$variation_count, $variation_count],
],
[
2, # artist_id
'横浜アリーナライブ',
[$variation_count, $variation_count],
],
[
2, # artist_id
'西武ドームライブ',
[$variation_count, $variation_count],
],
];
my $variation_names = [qw/アリーナ席 スタンド席/];
my $orders = [];
my $recent_stocks = [];
my $log_file = './logfile';
my $log;
my $content_index = '';
my $content_tickets = [];
{
# preload
if ( -e $log_file ) {
open $log, '<', $log_file;
my @lines = <$log>;
$log->close;
open $log, '>>', $log_file;
for my $line (@lines) {
chomp $line;
my $order = $json_decoder->decode($line);
buy_from_history($order->[3], $order->[1], $order->[4]);
}
}
else {
open $log, '>', $log_file;
}
$content_index = content_index();
my $ticket_id = 0;
for my $ticket ( @{$tickets} ) {
$ticket_id++;
$content_tickets->[$ticket_id-1] = content_ticket($ticket_id);
}
}
sub initial() {
$orders = [];
$recent_stocks = [];
for my $ticket ( @{$tickets} ) {
$ticket->[2] = [$variation_count, $variation_count];
}
close $log;
open $log, '>', $log_file;
$content_index = content_index();
my $ticket_id = 0;
for my $ticket ( @{$tickets} ) {
$ticket_id++;
$content_tickets->[$ticket_id-1] = content_ticket($ticket_id);
}
}
sub base_top() {
<<_EOF_;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>isucon 2</title>
<link type="text/css" rel="stylesheet" href="/css/ui-lightness/jquery-ui-1.8.24.custom.css">
<link type="text/css" rel="stylesheet" href="/css/isucon2.css">
<script type="text/javascript" src="/js/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="/js/jquery-ui-1.8.24.custom.min.js"></script>
<script type="text/javascript" src="/js/isucon2.js"></script>
</head>
<body>
<header>
<a href="/">
<img src="/images/isucon_title.jpg">
</a>
</header>
<div id="sidebar">
_EOF_
}
sub after_side() {
<<_EOF_;
</div>
<div id="content">
_EOF_
}
sub base_bottom() {
<<_EOF_;
</div>
</body>
</html>
_EOF_
}
sub content_admin() {
<<_EOF_;
<ul>
<li>
<a href="/admin/order.csv">注文CSV</a>
</li>
<li>
<form method="POST">
<input type="submit" value="データ初期化">
</form>
</li>
</ul>
_EOF_
}
sub sidebar {
my @body = ('<table><tr><th colspan="2">最近購入されたチケット</th></tr>');
for my $recent_stock ( @{$recent_stocks} ) {
push @body, sprintf(
'<tr>
<td class="recent_variation">%s</td>
<td class="recent_seat_id">%s</td>
</tr>',
@{$recent_stock},
);
}
push @body, '</table>';
return join '', @body;
}
sub content_index {
my @body = ('<h1>TOP</h1><ul>');
my $artist_id = 0;
for my $artist ( @{$artists} ) {
$artist_id++;
push @body, sprintf(
'<li>
<a href="/artist/%s">
<span class="artist_name">%s</span>
</a>
</li>',
$artist_id, $artist
);
}
return join '', @body, '</ul>';
}
sub content_artist {
my $artist_id = shift;
my @body = (
sprintf('<h2>%s</h2><ul>', $artists->[$artist_id-1])
);
my $ticket_id = 0;
for my $ticket ( @{$tickets} ) {
$ticket_id++;
next unless $ticket->[0] == $artist_id;
push @body, sprintf(
'<li class="ticket">
<a href="/ticket/%s">%s</a>残り<span class="count">%s</span>枚
</li>',
$ticket_id, $ticket->[1], sum @{$ticket->[2]},
);
}
return join '', @body, '</ul>';
}
sub content_ticket {
my $ticket_id = shift;
my $ticket = $tickets->[$ticket_id-1];
my $artist = $artists->[$ticket->[0]-1];
my @body = (
sprintf('<h2>%s</h2><ul>', $artist, $ticket->[1])
);
my @body2 = ('<h3>席状況</h3>');
my $variation_order = 0;
for my $variation_name ( @{$variation_names} ) {
$variation_order++;
my $variation_id = (($ticket_id - 1) * 2) + $variation_order;
my $vacancy = $ticket->[2][$variation_order-1];
push @body, sprintf(
'<li class="variation">
<form method="POST" action="/buy">
<input type="hidden" name="ticket_id" value="%s">
<input type="hidden" name="variation_id" value="%s">
<span class="variation_name">%s</span> 残り<span class="vacancy" id="vacancy_%s">%s</span>席
<input type="text" name="member_id" value="">
<input type="submit" value="購入">
</form>
</li>',
$ticket_id, $variation_id, $variation_name, $variation_id,
$vacancy,
);
push @body2, sprintf(
'<h4>%s</h4>
<table class="seats" data-variationid="%s">',
$variation_name, $variation_id
);
for my $row (0..63) {
push @body2, '<tr>';
for my $col (0..63) {
my $seat_order = $variation_count - (($row*64) + $col);
push @body2, sprintf(
'<td id="%02d-%02d" class="%s"></td>',
$row, $col,
($vacancy >= $seat_order) ? 'available' : 'unavailable',
);
}
push @body2, '</tr>';
}
push @body2, '</table>';
}
push @body, '</ul>';
return join '', @body, '</ul>', @body2;
}
sub buy_from_history {
my ( $variation_id, $member_id, $updated_at ) = @_;
my $variation_order = (2 - ($variation_id % 2));
my $ticket_id = int(($variation_id - 1) / 2) + 1;
my $ticket = $tickets->[$ticket_id-1];
my $vacancy = $ticket->[2][$variation_order-1];
my $variation_name = $variation_names->[$variation_order-1];
my $artist = $artists->[$ticket->[0]-1];
if ( $vacancy ) {
my $seat_order = $variation_count - $vacancy;
my $seat_id = sprintf('%02d-%02d', int($seat_order / 64), ($seat_order % 64));
$ticket->[2][$variation_order-1]--;
pop $recent_stocks if scalar @$recent_stocks >= 10;
unshift $recent_stocks, [
sprintf(
'%s %s %s',
$artist,
$ticket->[1],
$variation_name,
),
$seat_id
];
push $orders, [
(scalar @{$orders}) + 1,
$member_id,
$seat_id,
$variation_id,
$updated_at,
];
}
}
sub app {
my $env = shift;
my $method = $env->{REQUEST_METHOD};
my $path_info = $env->{PATH_INFO};
if ( $method eq 'GET' ) {
if ( $path_info eq '/' ) {
return ['200', $header, [
base_top(),
sidebar(),
after_side(),
$content_index,
base_bottom(),
]];
}
elsif ( $path_info =~ m{\A/artist/(\d+)\z}o ) {
return ['200', $header, [
base_top(),
sidebar(),
after_side(),
content_artist($1),
base_bottom(),
]];
}
elsif ( $path_info =~ m{\A/ticket/(\d+)\z}o ) {
return ['200', $header, [
base_top(),
sidebar(),
after_side(),
$content_tickets->[$1-1],
base_bottom(),
]];
}
elsif ( $path_info eq '/admin' ) {
return ['200', $header, [
base_top(),
after_side(),
content_admin(),
base_bottom(),
]];
}
elsif ( $path_info eq '/admin/order.csv' ) {
my @body = ();
for my $order (@$orders) {
push @body, join ',', @{$order};
push @body, "\n";
}
return ['200', ['content-type' => 'text/csv'], \@body];
}
}
elsif ( $method eq 'POST' ) {
if ( $path_info eq '/buy' ) {
my $param = Plack::Request->new($env)->body_parameters;
my $variation_id = $param->{variation_id};
my $member_id = $param->{member_id};
my $variation_order = (2 - ($variation_id % 2));
my $ticket_id = int(($variation_id - 1) / 2) + 1;
my $ticket = $tickets->[$ticket_id-1];
my $vacancy = $ticket->[2][$variation_order-1];
my $variation_name = $variation_names->[$variation_order-1];
my $artist = $artists->[$ticket->[0]-1];
if ( $vacancy ) {
my $seat_order = $variation_count - $vacancy;
my $seat_id = sprintf('%02d-%02d', int($seat_order / 64), ($seat_order % 64));
$ticket->[2][$variation_order-1]--;
pop $recent_stocks if scalar @$recent_stocks >= 10;
unshift $recent_stocks, [
sprintf(
'%s %s %s',
$artist,
$ticket->[1],
$variation_name,
),
$seat_id
];
my $order = [
(scalar @{$orders}) + 1,
$member_id,
$seat_id,
$variation_id,
strftime('%Y-%m-%d %H:%M:%S', localtime()),
];
push $orders, $order;
$log->printflush($json_encoder->encode($order), "\n");
$content_tickets->[$ticket_id-1] = content_ticket($ticket_id);
return ['200', $header, [
base_top(),
after_side(),
'<h2>予約完了</h2>',
sprintf(
'会員ID:<span class="member_id">%s</span>で<span class="result" data-result="success">&quot;<span class="seat">%s</span>&quot;の席を購入しました。</span>', $member_id, $seat_id),
base_bottom(),
]];
}
else {
return ['200', $header, [
base_top(),
after_side(),
'<span class="result" data-result="failure">売り切れました。</span>',
base_bottom(),
]];
}
}
elsif ( $path_info eq '/admin' ) {
initial();
return ['302', [Location => '/admin'], []];
}
}
return ['404', $header, ['not found']];
}
use Plack::Builder;
builder {
enable "ContentLength";
enable 'Static',
path => qr!^/(?:(?:css|js|images)/|favicon\.ico$)!,
root => './staticfiles/';
mount '/' => \&app;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment