Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Write a C++ application called NewTetris that allows a new way to play Tetris. I

ID: 3528846 • Letter: W

Question

Write a C++ application called NewTetris that allows a new way to play Tetris. It is well known that in Tetris there are seven kinds of tetriminos, each of which is made up of four minos. The application window is divided into three bands: top, middle and bottom. When the application starts, seven different tetriminos will appear within the top band, as shown in the Java applet below. These seven tetrinimos are referred to as model tetriminos. A user can add tetriminos into the middle band to create a meaningful shape. A tetrimino that is no longer useful can be deleted by putting it into the bottom band. Specifically, a user can perform the following four operations to create a meaningful shape in the middle band. Add: to add a tetrimino, a user can use the left mouse button to drag the corresponding model tetrimino located within the top band. When a model tetrimino is selected to be dragged, a duplicate is made out of the model. It is the duplicate that is actually dragged. The model tetrimino stays at the same location. Move: To move a duplicate tetrimino, use the left mouse button to drag it. The model tetrimino can't be moved. Rotate: To rotate a duplicate tetrimino, use the right mouse button to click one of its four minos. Please note the tetrimino will rotate about the center of the mino that is clicked. The model tetrimino can't be rotated. Delete: at the end of the moving or rotating operation, if the tetrimino intersects with the bottom band, the tetrimino will be deleted. Since a model tetrimino can't be moved or rotated, it can't be deleted. The follow Java applet is an implementation of NewTetris. Please try the applet to see how the application works. Requirements/Recommendations: You shall write a class named CMino to describe a mino. You shall write a class named CTetriMino to describe seven different tetriminos. You can use collections to keep a record of all the tetriminos added so far. Use event handlers to add, move, rotate and delete a tetrimino. After rotating a point (x1, y1) about another point (x0, y0) counterclockwise through an angle of theta in radians, its new location (x2, y2) can be represented as follows. x2=x0+(x1-x0)*cos(theta)-(y1-y0)*sin(theta) y2=y0+(x1-x0)*sin(theta)+(y1-y0)*cos(theta) the two codes we get to start with form1.h #pragma once #include "Piece.h" namespace Tangram { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; /// /// Summary for Form1 /// public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); // //TODO: Add the constructor code here // // Initial State Blocks = gcnew ArrayList(); Blocks->Add(gcnew CPiece(400, 300, 300, 200, 300, 400)); Blocks->Add(gcnew CPiece(400, 300, 300, 400, 500, 400)); Blocks->Add(gcnew CPiece(500, 200, 400, 200, 500, 300)); Blocks->Add(gcnew CPiece(400, 300, 450, 250, 350, 250)); Blocks->Add(gcnew CPiece(450, 350, 500, 400, 500, 300)); Blocks->Add(gcnew CPiece(400, 300, 450, 350, 500, 300, 450, 250)); Blocks->Add(gcnew CPiece(300, 200, 350, 250, 450, 250, 400, 200)); isShiftDown=false; isCtrlDown=false; BlockToBeMoved=nullptr; // no shape selected this->SetStyle(ControlStyles::Opaque, true); // for double buffering } protected: /// /// Clean up any resources being used. /// ~Form1() { if (components) { delete components; } } private: /// /// Required designer variable. /// System::ComponentModel::Container ^components; // state representation ArrayList ^Blocks; bool isShiftDown; bool isCtrlDown; CPiece^ BlockToBeMoved; int m_nOldX; int m_nOldY; #pragma region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// void InitializeComponent(void) { this->SuspendLayout(); // // Form1 // this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(800, 600); this->Name = L"Form1"; this->Text = L"Tangram"; this->Paint += gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::Form1_Paint); this->KeyDown += gcnew System::Windows::Forms::KeyEventHandler(this, &Form1::Form1_KeyDown); this->KeyUp += gcnew System::Windows::Forms::KeyEventHandler(this, &Form1::Form1_KeyUp); this->MouseClick += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseClick); this->MouseDown += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseDown); this->MouseMove += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseMove); this->MouseUp += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseUp); this->ResumeLayout(false); } #pragma endregion private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) { Bitmap ^backBuffer = gcnew Bitmap(ClientSize.Width, ClientSize.Height); Graphics ^gBackBuffer = Graphics::FromImage(backBuffer); gBackBuffer->Clear(Color::White); // draw the originals to back buffer for (int i=0; iCount; i++) { CPiece^ p=(CPiece^)Blocks[i]; p->draw(gBackBuffer); } // copy from back buffer to front e->Graphics->DrawImageUnscaled(backBuffer, 0, 0); } private: System::Void Form1_KeyDown(System::Object^ sender, System::Windows::Forms::KeyEventArgs^ e) { if (e->KeyCode==Keys::ShiftKey) isShiftDown=true; if (e->KeyCode==Keys::ControlKey) isCtrlDown=true; } private: System::Void Form1_KeyUp(System::Object^ sender, System::Windows::Forms::KeyEventArgs^ e) { if (e->KeyCode==Keys::ShiftKey) isShiftDown=false; if (e->KeyCode==Keys::ControlKey) isCtrlDown=false; } private: System::Void Form1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { if (isShiftDown || isCtrlDown) return; if (e->Button == System::Windows::Forms::MouseButtons::Left) { for (int i=Blocks->Count-1; i>=0; i--) { // search from last to first CPiece^ p=(CPiece^)Blocks[i]; if (p->isInside(e->X, e->Y)) { Blocks->RemoveAt(i); Blocks->Add(p); // move to the end, i.e. the top BlockToBeMoved=p; m_nOldX=e->X; m_nOldY=e->Y; Invalidate(); break; } } } } private: System::Void Form1_MouseMove(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { if (e->Button == System::Windows::Forms::MouseButtons::Left) { if (BlockToBeMoved!=nullptr) { BlockToBeMoved->translate(e->X-m_nOldX, e->Y-m_nOldY); m_nOldX=e->X; m_nOldY=e->Y; Invalidate(); } } } private: System::Void Form1_MouseUp(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { BlockToBeMoved=nullptr; // no shape selected } private: System::Void Form1_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { if (!isShiftDown && !isCtrlDown) return; if (e->Button == System::Windows::Forms::MouseButtons::Left) { for (int i=Blocks->Count-1; i>=0; i--) { CPiece^ p=(CPiece^)Blocks[i]; if (p->isInside(e->X, e->Y)) { p->rotate(isShiftDown?-5:5);// note difference btw coordinate systems Blocks->RemoveAt(i); Blocks->Add(p); // move to the end, i.e. the top Invalidate(); break; } } } } }; } Piece.h using namespace System; using namespace System::Drawing; public ref class CPiece { public: CPiece(int x0, int y0, int x1, int y1, int x2, int y2) { // for triangle Type=3; Xs=gcnew array(3); Ys=gcnew array(3); Xs[0]=x0; Ys[0]=y0; Xs[1]=x1; Ys[1]=y1; Xs[2]=x2; Ys[2]=y2; } CPiece(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) { // for quadrilateral Type=4; Xs=gcnew array(4); Ys=gcnew array(4); Xs[0]=x0; Ys[0]=y0; Xs[1]=x1; Ys[1]=y1; Xs[2]=x2; Ys[2]=y2; Xs[3]=x3; Ys[3]=y3; } CPiece(CPiece^ src) { // copy constructor Type=src->Type; Xs=gcnew array(Type); Ys=gcnew array(Type); for (int i=0; iXs[i]; Ys[i]=src->Ys[i]; } } private: bool isLeft(int x, int y, int id) { // Is Point (x, y) located to the left when walking from id to id+1? return (y-Ys[id])*(Xs[(id+1)%Type]-Xs[id])<(x-Xs[id])*(Ys[(id+1)%Type]-Ys[id]); // note difference btw coordinate systems } public: bool isInside(int x, int y) { for (int i=0; i^ Points = gcnew array(Type); for (int i=0; iFillPolygon(Brushes::Red, Points); g->DrawPolygon(Pens::Black, Points); } private: int Type; // 3: triangle; 4: quadrilateral array ^Xs; array ^Ys; };

Explanation / Answer

//draw the board
private void DrawBoard()
{

//clean up the base image because we need to write
//a new score to it
_baseimage=(Image)_cleanbase.Clone();

Graphics g=Graphics.FromImage(_baseimage);

g.SmoothingMode =SmoothingMode.AntiAlias;

//write the score on the base image
g.DrawString("Score:"+_score,new Font("Arial",12,
FontStyle.Bold),
new SolidBrush(_textcolor),
new Point(5,5));
g.Dispose();

//repaint the background with the _baseimage
DrawPicture();
//paint on the background with all the cells
g=Graphics.FromImage(this.BackgroundImage);

g.SmoothingMode =SmoothingMode.AntiAlias;

foreach(Cell c in _cells)
{
//if(!c.CellColor.Equals(Color.Black))
if(!c.Avail)
{

GraphicsPath p=new GraphicsPath();
p.AddEllipse(c.CellPoint.X*_blocksize,
c.CellPoint.Y*_blocksize,
_blocksize-3,_blocksize-3);
PathGradientBrush br=new PathGradientBrush(p);


br.CenterColor=c.CellColor;
br.SurroundColors=new Color[]{c.CellFillColor};


g.FillPath(br,p);
g.DrawPath(new Pen(c.CellFillColor,1),p);
br.Dispose();
p.Dispose();
}

}
....

//repaint the Background with the _baseimage
private void DrawPicture()
{
Graphics g=Graphics.FromImage(this.BackgroundImage);
g.SmoothingMode=SmoothingMode.AntiAlias;
g.FillRectangle(new SolidBrush(this.BackColor),
0,0,this.Width,this.Height);

if(_baseimage!=null)
{
g.DrawImage(_baseimage,new Rectangle(0,0,
_baseimage.Width,_baseimage.Height ),
new Rectangle(0,0,_baseimage.Width,
_baseimage.Height),
GraphicsUnit.Pixel );
}

if(g!=null) g.Dispose();
}

Rendering shape

When a TetrisShape object is visible, it is painted by its OnPaint() method. This is applicable for the TetrisShape object that is used for preview purpose.

protected override void OnPaint(PaintEventArgs e)
{
try
{
//this.BackColor=Color.Black;
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;

for(int i=0;i<_points.Length;i++)
{
GraphicsPath p=new GraphicsPath();
p.AddEllipse(_points[i].X*_blocksize,_points[i].Y*_blocksize,
_blocksize-2,_blocksize-2);
PathGradientBrush br=new PathGradientBrush(p);
br.CenterColor =this._color;
br.SurroundColors=new Color[]{this._fillcolor};
e.Graphics.DrawPath(new Pen(_color),p);
e.Graphics.FillPath(br,p);

br.Dispose();
p.Dispose();

}
}
catch(Exception ex){MessageBox.Show(ex.ToString());}

base.OnPaint (e);
}

For those TetrisShape objects that are used otherwise, they are not rendered directly. Rendering is done by the TetrisBoard onto its Background image by calling DrawShape() and EraseShape() methods.

//draw the shape onto the parent background image
//with the blocksize passed in
internal void DrawShape(int _blocksize)
{

Image img=((TetrisBoard)Parent).BackgroundImage;
Graphics g=Graphics.FromImage(img);

g.SmoothingMode=SmoothingMode.AntiAlias;

foreach(Point pt in _points)
{

GraphicsPath p=new GraphicsPath();
p.AddEllipse(pt.X*_blocksize+Location.X,
pt.Y*_blocksize+Location.Y,
_blocksize-3,_blocksize-3);
PathGradientBrush br=new PathGradientBrush(p);


br.CenterColor=_color;
br.SurroundColors=new Color[]{_fillcolor};

g.FillPath(br,p);
g.DrawPath(new Pen(_fillcolor,1),p);

br.Dispose();
p.Dispose();
}
g.Dispose();
((TetrisBoard)Parent).Refresh();
}


internal void EraseShape(int _blocksize)
{
Image img=((TetrisBoard)Parent).BackgroundImage;
Image _img=((TetrisBoard)Parent).StoredImage;

Graphics g=Graphics.FromImage(img);
//Graphics g=Graphics.FromHwnd(((TetrisBoard)Parent).Handle);


g.SmoothingMode=SmoothingMode.AntiAlias;
foreach(Point p in _points)
{

g.DrawImage(_img,p.X*_blocksize+Location.X,
p.Y*_blocksize+Location.Y,
new Rectangle(new Point(p.X*_blocksize+Location.X,
p.Y*_blocksize+Location.Y),
new Size(_blocksize,_blocksize)),
GraphicsUnit.Pixel);

}
g.Dispose();
}

Events

There is only one event that is defined for the TetrisBoard. It is the GameOver event which is fired (as the name suggests) when the game is over.

//Event Handler for Game Over
private EventHandler onGameOver;

//Method called to fire the onGameOver event
protected virtual void OnGameOver(EventArgs e)
{
if(this.onGameOver!=null)
this.onGameOver(this,e);
}


[Category("TetrisEvent"),Description("Game Over Event Handler")]
public event EventHandler GameOver
{
add{onGameOver += value;}
remove{onGameOver -=value;}
}

...
if(CheckGameOver(_ts))
{
Controls.Remove(_ts);
_gameover=true;
_gameactive=false;
DrawBoard();
//Fire the OnGameOver Event
OnGameOver(null);
return;
}

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote