#!/usr/bin/perl -w use strict; use Term::ReadKey; $|=1; # TODO: check for lack of valid moves and end game # TODO: maybe have a way to rotate/flip the board? my $quiet=0; $quiet=shift @ARGV if(@ARGV and "-q" eq $ARGV[0]); die "usage: $0 [-q]\n" if(@ARGV); my $savefile="$ENV{HOME}/.2048"; my $score=0; my @grid=(); sub addtile() { my $val=rand()<.9?2:4; my @poss=(); for(my $i=0;$i<@grid;$i++) { for(my $j=0;$j<@grid;$j++) { push @poss,"$i,$j" unless($grid[$i][$j]); } } die unless(@poss); my ($x,$y)=split /,/,$poss[int(rand(@poss))]; $grid[$x][$y]=$val; } sub resetgrid() { $score=0; @grid=(); push @grid,[0,0,0,0] while(@grid<4); addtile(); addtile(); } sub loadgrid() { open(GRID,"<",$savefile) or goto BAD_GRID; defined(my $line=) or goto BAD_GRID; close(GRID) or die "close: $!"; chomp $line; my @line=split / /,$line; if(17!=@line or grep !/^\d+$/,@line) { print STDERR "invalid saved state; ignoring\n"; sleep(1); goto BAD_GRID; } $score=shift @line; push @grid,[splice @line,0,4]; push @grid,[splice @line,0,4]; push @grid,[splice @line,0,4]; push @grid,[splice @line,0,4]; goto GOT_GRID; BAD_GRID: resetgrid(); GOT_GRID: return; } sub savegrid() { if($score) { open(GRID,">",$savefile) or return; print GRID join(" ",$score,map {@$_} @grid)."\n" or die "write: $!"; close(GRID) or die "close: $!"; } else { unlink($savefile); # ignore errors; it may not exist } } my $clear=`clear`; sub showgrid() { my $screen=$clear; for(my $i=0;$i<@grid;$i++) { for(my $j=0;$j<@{$grid[$i]};$j++) { $screen.="+"; $screen.="------"; } $screen.="+\n"; for(my $j=0;$j<@{$grid[$i]};$j++) { $screen.="|"; if($grid[$i][$j]) { $screen.=sprintf "%5u ",$grid[$i][$j]; } else { $screen.=sprintf "%5s ",""; } } $screen.="|\n"; } for(my $j=0;$j<@{$grid[$#grid]};$j++) { $screen.="+"; $screen.="------"; } $screen.="+\n"; $screen.="Score: $score\n"; print $screen; } sub smash(@) { my @in=grep {$_} @_; my @out=(); # Tiles shouldn't merge more than once per move while(@in) { if(1<@in and $in[0]==$in[1]) { $score+=$in[0]+$in[1]; push @out,$in[0]+$in[1]; splice @in,0,2; } else { push @out,shift @in; } } return @out; } sub shift_ud($) { my ($down)=@_; my $progress=0; for(my $j=0;$j<@{$grid[0]};$j++) { my @col=map {$grid[$_][$j]} 0..$#grid; @col=reverse(@col) if($down); @col=smash(@col); push @col,0 while(@col<4); @col=reverse(@col) if($down); map { $progress=1 if($grid[$_][$j] != $col[0]); $grid[$_][$j]=shift @col; } 0..$#grid; } return $progress; } sub shift_lr($) { my ($right)=@_; my $progress=0; for(my $i=0;$i<@grid;$i++) { my @row=map {$grid[$i][$_]} 0..$#{$grid[$i]}; @row=reverse(@row) if($right); @row=smash(@row); push @row,0 while(@row<4); @row=reverse(@row) if($right); map { $progress=1 if($grid[$i][$_] != $row[0]); $grid[$i][$_]=shift @row; } 0..$#{$grid[$i]}; } return $progress; } sub getdomove() { DO_AGAIN: # my $line=; # chomp $line; my $line=ReadKey(0); if("q" eq $line) { ReadMode('normal'); savegrid(); exit(0); } elsif("\x1B" eq $line) { goto DO_AGAIN; # it could be the start of an arrow key } elsif("[" eq $line) { goto DO_AGAIN; # it could be the start of an arrow key } elsif("A" eq $line) { # up arrow goto BAD_MOVE unless(shift_ud(0)); } elsif("B" eq $line) { # down arrow goto BAD_MOVE unless(shift_ud(1)); } elsif("C" eq $line) { # right arrow goto BAD_MOVE unless(shift_lr(1)); } elsif("D" eq $line) { # left arrow goto BAD_MOVE unless(shift_lr(0)); } elsif("N" eq $line) { # start new game resetgrid(); return(1); } else { print STDERR "Unknown move: $line\n"; goto DO_AGAIN; } return(0); BAD_MOVE: print STDERR "\7" unless($quiet); return(1); } loadgrid(); ReadMode('cbreak'); while(1) { showgrid(); next if(getdomove()); addtile(); }