-
-
Save SpringMT/6950668 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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">"<span class="seat">%s</span>"の席を購入しました。</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