Skip to content

Instantly share code, notes, and snippets.

@qzdc00
Last active August 29, 2015 14:10
Show Gist options
  • Save qzdc00/de338ec95694fdfde7e9 to your computer and use it in GitHub Desktop.
Save qzdc00/de338ec95694fdfde7e9 to your computer and use it in GitHub Desktop.
hw6/WashingtonPLMOOC
# University of Washington, Programming Languages, Homework 6, hw6runner.rb
# This is the only file you turn in, so do not modify the other files as
# part of your solution.
class MyPiece < Piece
# The constant All_My_Pieces should be declared here
All_My_Pieces = [[[[0, 0], [1, 0], [0, 1], [1, 1]]], # square (only needs one)
rotations([[0, 0], [-1, 0], [1, 0], [0, -1]]), # T
[[[0, 0], [-1, 0], [1, 0], [2, 0]], # long (only needs two)
[[0, 0], [0, -1], [0, 1], [0, 2]]],
rotations([[0, 0], [0, -1], [0, 1], [1, 1]]), # L
rotations([[0, 0], [0, -1], [0, 1], [-1, 1]]), # inverted L
rotations([[0, 0], [-1, 0], [0, -1], [1, -1]]), # S
rotations([[0, 0], [1, 0], [0, -1], [-1, -1]]), # Z
rotations([[0, 0], [0, 1], [1, 0], [0, -1], [1, -1]]), #good
[[[0, 0], [-1, 0], [-2, 0], [1, 0], [2, 0]], # longlong (only needs two)
[[0, 0], [0, -1], [0, -2], [0, 1], [0, 2]]],
rotations([[0, 0], [0, 1], [1, 0]])] #v
# your enhancements here
def initialize (point_array, board)
super
end
def self.next_piece(board)
MyPiece.new(All_My_Pieces.sample, board)
end
end
class MyBoard < Board
# your enhancements here
#attr_accessor :cheat
def initialize (game)
super
@current_block = MyPiece.next_piece(self)
@cheat = false
end
def rotate_clockwise180
if !game_over? and @game.is_running?
@current_block.move(0, 0, 2)
end
draw
end
def cheatblock
if !game_over? and @game.is_running? and score >= 100
@cheat = true
end
end
def next_piece
if(@cheat == true)
@current_block = MyPiece.new([[[0, 0]]], self)
@score -= 100
@game.update_score
@cheat = false
else
@current_block = MyPiece.next_piece(self)
end
@current_pos = nil
end
def store_current
locations = @current_block.current_rotation
displacement = @current_block.position
(0..locations.size-1).each{|index|
current = locations[index]
@grid[current[1]+displacement[1]][current[0]+displacement[0]] = @current_pos[index]
}
remove_filled
@delay = [@delay - 2, 80].max
end
end
class MyTetris < Tetris
# your enhancements here
def set_board
@canvas = TetrisCanvas.new
@board = MyBoard.new(self)
@canvas.place(@board.block_size * @board.num_rows + 3,
@board.block_size * @board.num_columns + 6, 24, 80)
@board.draw
end
def key_bindings
super
@root.bind('u', proc {@board.rotate_clockwise180})
@root.bind('c', proc {@board.cheatblock})
end
def buttons
super
rotate_clock180 = TetrisButton.new('r180', 'lightgreen'){@board.rotate_clockwise180}
rotate_clock180.place(35, 50, 27, 501)
cheatblock = TetrisButton.new('[]', 'lightgreen'){@board.cheatblock}
cheatblock.place(35, 50, 127, 501)
end
end
# University of Washington, Programming Languages, Homework 6, hw6graphics.rb
# This file provides an interface to a wrapped Tk library. The auto-grader will
# swap it out to use a different, non-Tk backend.
require 'tk'
class TetrisRoot
def initialize
@root = TkRoot.new('height' => 615, 'width' => 205,
'background' => 'lightblue') {title "Tetris"}
end
def bind(char, callback)
@root.bind(char, callback)
end
# Necessary so we can unwrap before passing to Tk in some instances.
# Student code MUST NOT CALL THIS.
attr_reader :root
end
class TetrisTimer
def initialize
@timer = TkTimer.new
end
def stop
@timer.stop
end
def start(delay, callback)
@timer.start(delay, callback)
end
end
class TetrisCanvas
def initialize
@canvas = TkCanvas.new('background' => 'grey')
end
def place(height, width, x, y)
@canvas.place('height' => height, 'width' => width, 'x' => x, 'y' => y)
end
def unplace
@canvas.unplace
end
def delete
@canvas.delete
end
# Necessary so we can unwrap before passing to Tk in some instances.
# Student code MUST NOT CALL THIS.
attr_reader :canvas
end
class TetrisLabel
def initialize(wrapped_root, &options)
unwrapped_root = wrapped_root.root
@label = TkLabel.new(unwrapped_root, &options)
end
def place(height, width, x, y)
@label.place('height' => height, 'width' => width, 'x' => x, 'y' => y)
end
def text(str)
@label.text(str)
end
end
class TetrisButton
def initialize(label, color)
@button = TkButton.new do
text label
background color
command (proc {yield})
end
end
def place(height, width, x, y)
@button.place('height' => height, 'width' => width, 'x' => x, 'y' => y)
end
end
class TetrisRect
def initialize(wrapped_canvas, a, b, c, d, color)
unwrapped_canvas = wrapped_canvas.canvas
@rect = TkcRectangle.new(unwrapped_canvas, a, b, c, d,
'outline' => 'black', 'fill' => color)
end
def remove
@rect.remove
end
def move(dx, dy)
@rect.move(dx, dy)
end
end
def mainLoop
Tk.mainloop
end
def exitProgram
Tk.exit
end
# University of Washington: Programming Languages
# Homework 6: A working Tetris game you will extend without breaking.
require_relative './hw6graphics'
# class responsible for the pieces and their movements
class Piece
# creates a new Piece from the given point array, holding the board for
# determining if movement is possible for the piece, and gives the piece a
# color, rotation, and starting position.
def initialize (point_array, board)
@all_rotations = point_array
@rotation_index = (0..(@all_rotations.size-1)).to_a.sample
@color = All_Colors.sample
@base_position = [5, 0] # [column, row]
@board = board
@moved = true
end
def current_rotation
@all_rotations[@rotation_index]
end
def moved
@moved
end
def position
@base_position
end
def color
@color
end
def drop_by_one
@moved = move(0, 1, 0)
end
# takes the intended movement in x, y and rotation and checks to see if the
# movement is possible. If it is, makes this movement and returns true.
# Otherwise returns false.
def move (delta_x, delta_y, delta_rotation)
# Ensures that the rotation will always be a possible formation (as opposed
# to nil) by altering the intended rotation so that it stays
# within the bounds of the rotation array
moved = true
potential = @all_rotations[(@rotation_index + delta_rotation) % @all_rotations.size]
# for each individual block in the piece, checks if the intended move
# will put this block in an occupied space
potential.each{|posns|
if !(@board.empty_at([posns[0] + delta_x + @base_position[0],
posns[1] + delta_y + @base_position[1]]));
moved = false;
end
}
if moved
@base_position[0] += delta_x
@base_position[1] += delta_y
@rotation_index = (@rotation_index + delta_rotation) % @all_rotations.size
end
moved
end
# class method to figures out the different rotations of the provided piece
def self.rotations (point_array)
rotate1 = point_array.map {|x,y| [-y,x]}
rotate2 = point_array.map {|x,y| [-x,-y]}
rotate3 = point_array.map {|x,y| [y,-x]}
[point_array, rotate1, rotate2, rotate3]
end
# class method to choose the next piece
def self.next_piece (board)
Piece.new(All_Pieces.sample, board)
end
# class array holding all the pieces and their rotations
All_Pieces = [[[[0, 0], [1, 0], [0, 1], [1, 1]]], # square (only needs one)
rotations([[0, 0], [-1, 0], [1, 0], [0, -1]]), # T
[[[0, 0], [-1, 0], [1, 0], [2, 0]], # long (only needs two)
[[0, 0], [0, -1], [0, 1], [0, 2]]],
rotations([[0, 0], [0, -1], [0, 1], [1, 1]]), # L
rotations([[0, 0], [0, -1], [0, 1], [-1, 1]]), # inverted L
rotations([[0, 0], [-1, 0], [0, -1], [1, -1]]), # S
rotations([[0, 0], [1, 0], [0, -1], [-1, -1]])] # Z
# class array
All_Colors = ['DarkGreen', 'dark blue', 'dark red', 'gold2', 'Purple3',
'OrangeRed2', 'LightSkyBlue']
end
# Class responsible for the interaction between the pieces and the game itself
class Board
def initialize (game)
@grid = Array.new(num_rows) {Array.new(num_columns)}
@current_block = Piece.next_piece(self)
@score = 0
@game = game
@delay = 500
end
# both the length and the width of a block, since it is a square
def block_size
15
end
def num_columns
10
end
def num_rows
27
end
# the current score
def score
@score
end
# the current delay
def delay
@delay
end
# the game is over when there is a piece extending into the second row
# from the top
def game_over?
@grid[1].any?
end
# moves the current piece down by one, if this is not possible stores the
# current piece and replaces it with a new one.
def run
ran = @current_block.drop_by_one
if !ran
store_current
if !game_over?
next_piece
end
end
@game.update_score
draw
end
# moves the current piece left if possible
def move_left
if !game_over? and @game.is_running?
@current_block.move(-1, 0, 0)
end
draw
end
# moves the current piece right if possible
def move_right
if !game_over? and @game.is_running?
@current_block.move(1, 0, 0)
end
draw
end
# rotates the current piece clockwise
def rotate_clockwise
if !game_over? and @game.is_running?
@current_block.move(0, 0, 1)
end
draw
end
# rotates the current piece counterclockwise
def rotate_counter_clockwise
if !game_over? and @game.is_running?
@current_block.move(0, 0, -1)
end
draw
end
# drops the piece to the lowest location in the currently occupied columns.
# Then replaces it with a new piece
# Change the score to reflect the distance dropped.
def drop_all_the_way
if @game.is_running?
ran = @current_block.drop_by_one
@current_pos.each{|block| block.remove}
while ran
@score += 1
ran = @current_block.drop_by_one
end
draw
store_current
if !game_over?
next_piece
end
@game.update_score
draw
end
end
# gets the next piece
def next_piece
@current_block = Piece.next_piece(self)
@current_pos = nil
end
# gets the information from the current piece about where it is and uses this
# to store the piece on the board itself. Then calls remove_filled.
def store_current
locations = @current_block.current_rotation
displacement = @current_block.position
(0..3).each{|index|
current = locations[index];
@grid[current[1]+displacement[1]][current[0]+displacement[0]] =
@current_pos[index]
}
remove_filled
@delay = [@delay - 2, 80].max
end
# Takes a point and checks to see if it is in the bounds of the board and
# currently empty.
def empty_at (point)
if !(point[0] >= 0 and point[0] < num_columns)
return false
elsif point[1] < 1
return true
elsif point[1] >= num_rows
return false
end
@grid[point[1]][point[0]] == nil
end
# removes all filled rows and replaces them with empty ones, dropping all rows
# above them down each time a row is removed and increasing the score.
def remove_filled
(2..(@grid.size-1)).each{|num| row = @grid.slice(num);
# see if this row is full (has no nil)
if @grid[num].all?
# remove from canvas blocks in full row
(0..(num_columns-1)).each{|index|
@grid[num][index].remove;
@grid[num][index] = nil
}
# move down all rows above and move their blocks on the canvas
((@grid.size - num + 1)..(@grid.size)).each{|num2|
@grid[@grid.size - num2].each{|rect| rect && rect.move(0, block_size)};
@grid[@grid.size-num2+1] = Array.new(@grid[@grid.size - num2])
}
# insert new blank row at top
@grid[0] = Array.new(num_columns);
# adjust score for full flow
@score += 10;
end}
self
end
# current_pos holds the intermediate blocks of a piece before they are placed
# in the grid. If there were any before, they are sent to the piece drawing
# method to be removed and replaced with that of the new position
def draw
@current_pos = @game.draw_piece(@current_block, @current_pos)
end
end
class Tetris
# creates the window and starts the game
def initialize
@root = TetrisRoot.new
@timer = TetrisTimer.new
set_board
@running = true
key_bindings
buttons
run_game
end
# creates a canvas and the board that interacts with it
def set_board
@canvas = TetrisCanvas.new
@board = Board.new(self)
@canvas.place(@board.block_size * @board.num_rows + 3,
@board.block_size * @board.num_columns + 6, 24, 80)
@board.draw
end
def key_bindings
@root.bind('n', proc {self.new_game})
@root.bind('p', proc {self.pause})
@root.bind('q', proc {exitProgram})
@root.bind('a', proc {@board.move_left})
@root.bind('Left', proc {@board.move_left})
@root.bind('d', proc {@board.move_right})
@root.bind('Right', proc {@board.move_right})
@root.bind('s', proc {@board.rotate_clockwise})
@root.bind('Down', proc {@board.rotate_clockwise})
@root.bind('w', proc {@board.rotate_counter_clockwise})
@root.bind('Up', proc {@board.rotate_counter_clockwise})
@root.bind('space' , proc {@board.drop_all_the_way})
end
def buttons
pause = TetrisButton.new('pause', 'lightcoral'){self.pause}
pause.place(35, 50, 90, 7)
new_game = TetrisButton.new('new game', 'lightcoral'){self.new_game}
new_game.place(35, 75, 15, 7)
quit = TetrisButton.new('quit', 'lightcoral'){exitProgram}
quit.place(35, 50, 140, 7)
move_left = TetrisButton.new('left', 'lightgreen'){@board.move_left}
move_left.place(35, 50, 27, 536)
move_right = TetrisButton.new('right', 'lightgreen'){@board.move_right}
move_right.place(35, 50, 127, 536)
rotate_clock = TetrisButton.new('^_)', 'lightgreen'){@board.rotate_clockwise}
rotate_clock.place(35, 50, 77, 501)
rotate_counter = TetrisButton.new('(_^', 'lightgreen'){
@board.rotate_counter_clockwise}
rotate_counter.place(35, 50, 77, 571)
drop = TetrisButton.new('drop', 'lightgreen'){@board.drop_all_the_way}
drop.place(35, 50, 77, 536)
label = TetrisLabel.new(@root) do
text 'Current Score: '
background 'lightblue'
end
label.place(35, 100, 26, 45)
@score = TetrisLabel.new(@root) do
background 'lightblue'
end
@score.text(@board.score)
@score.place(35, 50, 126, 45)
end
# starts the game over, replacing the old board and score
def new_game
@canvas.unplace
@canvas.delete
set_board
@score.text(@board.score)
@running = true
run_game
end
# pauses the game or resumes it
def pause
if @running
@running = false
@timer.stop
else
@running = true
self.run_game
end
end
# alters the displayed score to reflect what is currently stored in the board
def update_score
@score.text(@board.score)
end
# repeatedly calls itself so that the process is fully automated. Checks if
# the game is over and if it isn't, calls the board's run method which moves
# a piece down and replaces it with a new one when the old one can't move any
# more
def run_game
if !@board.game_over? and @running
@timer.stop
@timer.start(@board.delay, (proc{@board.run; run_game}))
end
end
# whether the game is running
def is_running?
@running
end
# takes a piece and optionally the list of old TetrisRects corresponding
# to it and returns a new set of TetrisRects which are how the piece is
# visible to the user.
def draw_piece (piece, old=nil)
if old != nil and piece.moved
old.each{|block| block.remove}
end
size = @board.block_size
blocks = piece.current_rotation
start = piece.position
blocks.map{|block|
TetrisRect.new(@canvas, start[0]*size + block[0]*size + 3,
start[1]*size + block[1]*size,
start[0]*size + size + block[0]*size + 3,
start[1]*size + size + block[1]*size,
piece.color)}
end
end
# To help each game of Tetris be unique.
srand
# University of Washington, Programming Languages, Homework 6, hw6runner.rb
require_relative './hw6provided'
require_relative './hw6assignment'
def runTetris
Tetris.new
mainLoop
end
def runMyTetris
MyTetris.new
mainLoop
end
if ARGV.count == 0
runMyTetris
elsif ARGV.count != 1
puts "usage: hw6runner.rb [enhanced | original]"
elsif ARGV[0] == "enhanced"
runMyTetris
elsif ARGV[0] == "original"
runTetris
else
puts "usage: hw6runner.rb [enhanced | original]"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment