#StackBounty: #c# #object-oriented #design-patterns #.net #memory-management Implement IDisposable correctly using object composition p…

Bounty: 50

Is it possible to implement IDisposable pattern correctly while using object composition principle to promote code-reuse, reduce code duplication and hide verbose "official" implementation?

Rational

Proposal

Delegate the dispose logic to a dedicated class:

public class DisposeManager
{
    public Action Managed { get; set; }
    public Action Unmanaged { get; set; }

    protected virtual void Dispose(bool disposing)
    {
        // only dispose once
        if (disposed)
            return;

        if (disposing)
        {
            Managed?.Invoke();
        }

        Unmanaged?.Invoke();

        disposed = true; 
    }

    public void DisposeObject(object o)
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(o);
    }

    public void FinalizeObject()
    {
        Dispose(disposing: false);
    }

    private bool disposed;
}

Implement IDisposable in user class in the following way:

public class DisposeUser : IDisposable
{
    public DisposeUser()
    {
        // using lambda
        disposeManager.Managed = () =>
        {
            // [...]
        };

        // or using member method
        disposeManager.Unmanaged = DisposeUnmanaged;
    }

    ~DisposeUser()
    {
        disposeManager.FinalizeObject();
    }

    public void Dispose()
    {
        disposeManager.DisposeObject(this);
    }

    private void DisposeUnmanaged()
    {
        // [...]
    }

    private readonly DisposeManager disposeManager = new DisposeManager();
}

Benefits

  • much simpler to implement for user classes
  • more explicit (managed, unmanaged)
  • use composition
  • remove the needs for multiple base classes all implementing the dispose pattern and creating code duplication

Questions

  • Is it ever a good idea or more of a programmer fancy "improvement" idea ?
  • I’ve made a decent number of research on the dispose pattern and implementation but never found someone suggesting such idea, any reason why?
  • Any potential problems around hard refs, especially with Action capturing members, etc. that would prevent the actual user class to be collected correctly?
  • Other thought?

Thanks!


Get this bounty!!!

#StackBounty: #python #object-oriented #design-patterns #flask Python: separation of concern User Model and User DB

Bounty: 150

I am working on a small side-project since a couple of weeks, I am using Flask but trying to use as few libraries as possible and no ORMs (for learning purposes.

I am currently working on the User service on my application and currently stuck in the registration email confirmation part regarding my design.

Since I am not using any ORM, I wrote those classes:

  • UserRepository: interact with the user table in the database (add, get, delete user and save)
  • UserService: actions on a User (registration, deactivate user, update stats)
  • User: representation of a User entity, no methods, methods are in the UserService (most of them take a User as a parameter expect the one to create a new User)

Current issue:

When a new user register, I send a registration email with a URL link to activate the account. I am using URLSafeTimedSerializer to do that and dump the user.email to generate the account confirmation URL.
The email is sent and when the user click on the link, I am able to decrypt the user.email using URLSafeTimedSerializer.loads(). But with my current design, I am not quite confident how I am going to retrieve the user.email. Maybe the UserService class should take the UserRepository as an argument?

user/views.py

user = Blueprint('user', __name__, template_folder='templates')

# ----------
# STUCK HERE 
# ----------
@user.route('/activate/<activation_token>', methods=["GET"])
def activate(activation_token):
    private_key = current_app.config['SECRET_KEY']
    s = URLSafeTimedSerializer(private_key)
    user_email = s.loads(activation_token)
    # now I need to check if that user is already confirmed, if not I have to change current User confirmed value from from False to True
    # I should probably create a new UserService instance and create a method get_user_by_email(email) ?

   def generate_registration_token(email):
    private_key = current_app.config['SECRET_KEY']
    s = URLSafeTimedSerializer(private_key)
    u = s.dumps(email)
    return u

def send_registration_email(email):
    from prepsmarter.blueprints.user.tasks import deliver_contact_email
    token = generate_registration_token(email)
    host = current_app.config['HOST']
    activation_url = f"{host}/activate/{token}"
    try:
        deliver_contact_email(email, activation_url)
        return "ok"
    except Exception as e:
        return str(e)
    
@user.route('/register')
def login():
    return render_template('register.html')

@user.route('/new-user',methods = ['POST'])
def register_user():
    form_email = request.form.get('email')
    form_password = request.form.get('psw')
    form_password_repeat = request.form.get('psw-repeat')
    registration_form = RegistrationForm(form_email, form_password, form_password_repeat).validate_registration()
    if registration_form:
        new_user = UserService().register_user(form_email, form_password)
        user_repository = UserRepository(conn, 'users')
        user_repository.add_user(new_user)
        user_repository.save()
        send_registration_email(new_user.email)
        return "new user created" #will probably change return statements later on
    return "new uer not created" #will probably change return statements later on

user/models.py (User class)

class User():
    def __init__(self, email, password, registration_date, active, sign_in_count, current_sign_in_on, last_sign_in_on):
        self.email = email
        self.password = password
        self.registration_date = registration_date
        self.active = active
        self.confirmed = False

user/services.py (UserRepository)

class UserRepository():
    def __init__(self, conn, table):
        self.conn = conn
        self.table = table  
    
    #select exists(select 1 from users where email='pamousset75@gmail.com')
    def add_user(self, user):
        sql = "INSERT INTO users (email, password, is_active, sign_in_count, current_sign_in_on, last_sign_in_on) VALUES (%s, %s, %s, %s, %s, %s)"
        cursor = self.conn.cursor()
        # the is_active column in the DB is a tinyint(1). True = 1 and False = 0
        if user.active == True:
            is_active = 1
        is_active = 0
        cursor.execute(sql, ( user.email, user.password, is_active, user.sign_in_count, user.current_sign_in_on, user.last_sign_in_on))
        resp = cursor.fetchall()
        return resp
    
    def delete_user(self):
        return ""
    
    def get_user(self):
        return ""
    
    def save(self):
        self.conn.commit()
    

user/services.py (UserService)

class UserService():

def register_user(self,
                  email,
                  password):
    sign_in_count = 1
    today_date = datetime.today().strftime('%Y-%m-%d')
    active = True
    new_user = User(email, password, today_date, active,
                    sign_in_count, today_date, today_date)
    return new_user        

def desactivate_user(self, User):
    if User.active == False:
        print(f"User {User.email} is already inactive")
    User.active = False

def reactive_user(self, User):
    if User.active == True:
        print(f"User {User.email} is already active")
    User.active = True

def is_active(self, User):
    return User.is_active


Get this bounty!!!

#StackBounty: #javascript #object-oriented #classes Javascript Object Oriented Planner App

Bounty: 50

I am creating a planner/scheduling app using objects. I am just a beginner with object oriented programming, so any tips on how to better structure my classes would be very helpful.


Program Basics

The program consists of "objectives" and "checkpoints." An objective is essentially a goal, and checkpoints are smaller steps that must be completed to reach that goal. Objectives and checkpoints all have "deadlines" (due dates) and are part of the calendar object. Eventually, I’d like to make a calendar display that shows the objectives and checkpoints.


Code

// GUID generator
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

// Adds add day feature to Date prototype
Date.prototype.addDays = function(days) {
    var date = new Date(this.valueOf());
    date.setDate(date.getDate() + days);
    return date;
}

// Parent object for all data
var calendar = {
    // Contains all objectives
    objectives: new Array(),
    // Checkpoints must be completed in order?
    strict: true,
    // Create objective, add to list, and sort list by deadline
    addObjective: function(name, deadline, description) {
        this.objectives.push(new Objective(name, deadline, description));
        this.sortObjectives();
    },
    getObjectives: function() {
        return this.objectives;
    },
    setStrict: function(strict) {
        this.strict = strict;
    },
    // Sort objectives by deadline, most recent last
    sortObjectives() {
        this.objectives.sort((a, b) => (a.deadline > b.deadline) ? 1 : -1);
    }
}

class Objective {
    constructor(name, deadline, description) {
        this.name = name;
        this.deadline = new Date(deadline);
        this.description = description;
        this.checkpoints = new Array();
        this.status = false;
        this.id = uuidv4();
    }
    // Push back deadline
    moveDeadline(days=0, moveAll=false) {
        this.deadline = this.deadline.addDays(days)
        // Move all checkpoints after current date unless incomplete
        if (moveAll) {
            let today = new Date();
            for (let i = 0; i < this.checkpoints.length; i++) {
                if (this.checkpoints[i].deadline < today || !this.checkpoints[i].status) {
                    this.checkpoints[i].deadline.addDays(days);
                }
            }
        }
    }
    // Remove objective from calendar object
    delete() {
        calendar.objectives.splice(calendar.objectives.indexOf(this), 1);
    }
    // Create checkpoint, add to list, sort order by deadline
    addCheckpoint(name, deadline, description) {
        this.checkpoints.push(new Checkpoint(name, deadline, description, this.id));
        this.sortCheckpoints();
    }
    getName() {
        return this.name;
    }
    setName(name) {
        this.name = name;
    }
    getDeadline() {
        return this.deadline.toDateString();
    }
    setDeadline(deadline) {
        this.deadline = new Date(deadline);
    }
    getDescription() {
        return this.description;
    }
    setDescription(description) {
        this.description = description;
    }
    getStatus() {
        return this.status;
    }
    // Checks whether all checkpoints are complete and determines status, invoked by Checkpoint.prototype.setStatus()
    setStatus() {
        let checkpoints = this.getCheckpoints();
        let allComplete = true;
        // Iterates through checkpoints
        for (let i = 0; i < checkpoints.length; i++) {
            // If at least one checkpoint is incomplete, objective status is false
            if (!checkpoints[i].status) {
                allComplete = false;
                break;
            }
        }
        if (allComplete) {
            this.status = true;
        }
        else {
            this.status = false;
        }
    }
    getCheckpoints() {
        return this.checkpoints;
    }
    getid() {
        return this.id;
    }
    // Sort checkpoints by deadline, most recent last
    sortCheckpoints() {
        this.checkpoints.sort((a, b) => (a.deadline > b.deadline) ? 1 : -1);
    }
}

class Checkpoint {
    constructor(name, deadline, description, id) {
        this.name = name;
        this.deadline = new Date(deadline);
        this.description = description;
        this.status = false;
        this.id = uuidv4();
        this.objectiveid = id;
    }
    // Push back deadline
    moveDeadline(days=0, moveAll=false) {
        this.deadline = this.deadline.addDays(days)
        // Move all checkpoints after this checkpoint deadline
        if (moveAll) {
            for (let i = 0; i < this.getObjective().getCheckpoints().length; i++) {
                if (this.getObjective().getCheckpoints()[i].deadline <= this.deadline && this.getObjective().getCheckpoints()[i] != this) {
                    this.getObjective().getCheckpoints()[i].deadline.addDays(days);
                }
            }
        }
    }
    // Remove checkpoint from objective object
    delete() {
        let objective = this.getObjective();
        objective.checkpoints.splice(objective.checkpoints.indexOf(this), 1);
    }
    getName() {
        return this.name;
    }
    setName(name) {
        this.name = name;
    }
    getDeadline() {
        return this.deadline.toDateString();
    }
    setDeadline(deadline) {
        this.deadline = new Date(deadline);
    }
    getDescription() {
        return this.description;
    }
    setDescription(description) {
        this.description = description;
    }
    getStatus() {
        return this.status;
    }
    // Update checkpoint status
    setStatus(status) {
        if (status == true) {
            if (calendar.strict) {
                let previousCheckpoints = this.getObjective().getCheckpoints().slice(0, this.getObjective().getCheckpoints().indexOf(this));
                let strictCondition = true;
                // Checks if all preceding checkpoints are completed if "strict" progress is enabled
                for (let i = 0; i < previousCheckpoints.length; i++) {
                    if (!previousCheckpoints[i].status) {
                        strictCondition = false;
                        break;
                    }
                }
                if (strictCondition) {
                    this.status = true;
                }
                else {
                    console.log('must complete preceding checkpoints');
                }
            }
            else {
                this.status = true;
            }
        }
        // No conditions for reverting checkpoint to incomplete
        else if (status == false) {
            this.status = false;
        }
        // Check objective status
        this.getObjective().setStatus();
    }
    getid() {
        return this.id;
    }
    getObjectiveid() {
        return this.objectiveid;
    }
    // Return reference to parent objective object
    getObjective() {
        for (let i = 0; i < calendar.objectives.length; i++) {
            let objective = calendar.objectives[i]
            if (objective.getid() == this.objectiveid) {
                return(objective);
            }
        }
    }
}
```


Get this bounty!!!

#StackBounty: #c# #object-oriented #snake-game Simple Snake Game With OOP in Mind

Bounty: 100

I am a beginner in C# and trying to learn how to program with OOP in mind. I am 100% that the code is not OOP-friendly at all, but the game is running and working, so if anyone could look throw the code and give me feedback on how to improve it, that would be helpful.

The game is a simple snake game with three players option. I have done two players part, but the code seems to be ugly and messy, so I didn’t want to add anything more to the garbage, and I am completely ok if I have to redo everything I am doing this to learn anyways

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace snakeGame3players
{
    public partial class Form1 : Form
    {
        private readonly List<Square> SnakeBoddy = new List<Square>();
        private readonly List<Square> SnakeBoddy2 = new List<Square>();
        private readonly List<Square> SnakeFoods1 = new List<Square>();
        private readonly List<Square> SnakeFoods2 = new List<Square>();
        private readonly List<Square> SnakeFoods3 = new List<Square>();
        private readonly List<Square> SnakeFoods4 = new List<Square>();
        System.Windows.Forms.Timer eventTimer = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer foodTimer = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer foodTimer2 = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer foodTimer3 = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer foodTimer4 = new System.Windows.Forms.Timer();
        Random random = new Random();
        bool exitFlag = false;


        public Form1()
        {
            InitializeComponent();
            choice1.Appearance = System.Windows.Forms.Appearance.Button;
            choice2.Appearance = System.Windows.Forms.Appearance.Button;
            choice3.Appearance = System.Windows.Forms.Appearance.Button;
            choice1.Size = new Size(154, 56);
            choice2.Size = new Size(154, 56);
            choice3.Size = new Size(154, 56);
            new Player1setting();
            new Player2setting();
            new FoodSetting();
            GameTimer.Interval = 100;
            this.KeyPreview = true;
            GameTimer.Tick += UpdateScreen;
            GameTimer.Start();
            label3.Visible = false;
            label2.Visible = false;

        }

        private void UpdateScreen(object sender, EventArgs e)
        {

            if (label1.Visible == true)
            {
                if (KeyInput.KeyInputs(Keys.Q))
                {

                    Application.Restart();
                    Environment.Exit(0);
                }
            }
            if (choice2.Checked && Player1setting.Gameover ==false)
            {
                if (KeyInput.KeyInputs(Keys.Right) && Player1setting.Movment != SnakeMovment.Left)
                {
                    Player1setting.Movment = SnakeMovment.Right;
                }

                else if (KeyInput.KeyInputs(Keys.Left) && Player1setting.Movment != SnakeMovment.Right)
                {
                    Player1setting.Movment = SnakeMovment.Left;
                }

                else if (KeyInput.KeyInputs(Keys.Up) && Player1setting.Movment != SnakeMovment.Down)
                {
                    Player1setting.Movment = SnakeMovment.Up;
                }

                else if (KeyInput.KeyInputs(Keys.Down) && Player1setting.Movment != SnakeMovment.Up)
                {
                    Player1setting.Movment = SnakeMovment.Down;
                }

                PlayerMovment();
            }

            pictureBox1.Invalidate();

            
            if (choice1.Checked && Player2setting.Gameover2 == false)
            {
                if (KeyInput.KeyInputs(Keys.D) && Player2setting.Movment2 != SnakeMovment2.A)
                {
                    Player2setting.Movment2 = SnakeMovment2.D;
                }

                else if (KeyInput.KeyInputs(Keys.A) && Player2setting.Movment2 != SnakeMovment2.D)
                {
                    Player2setting.Movment2 = SnakeMovment2.A;
                }

                else if (KeyInput.KeyInputs(Keys.W) && Player2setting.Movment2 != SnakeMovment2.S)
                {
                    Player2setting.Movment2 = SnakeMovment2.W;
                }

                else if (KeyInput.KeyInputs(Keys.S) && Player2setting.Movment2 != SnakeMovment2.W)
                {
                    Player2setting.Movment2 = SnakeMovment2.S;
                }

                PlayerMovment2();
            }

            pictureBox1.Invalidate();

        }

        private void PlayerMovment()
        {
            if (choice2.Checked) {
                for (int i = SnakeBoddy.Count - 1; i >= 0; i--)
                {
                    if (i == 0)
                    {
                        switch (Player1setting.Movment)
                        {
                            case SnakeMovment.Right:
                                SnakeBoddy[i].X++;
                                break;

                            case SnakeMovment.Left:
                                SnakeBoddy[i].X--;
                                break;

                            case SnakeMovment.Up:
                                SnakeBoddy[i].Y--;
                                break;

                            case SnakeMovment.Down:
                                SnakeBoddy[i].Y++;
                                break;
                        }

                        int maxXPosition = pictureBox1.Size.Width / Player1setting.Width;
                        int maxYPosition = pictureBox1.Size.Height / Player1setting.Height;

                        if (
                            SnakeBoddy[i].X < 0 || SnakeBoddy[i].Y < 0 ||
                            SnakeBoddy[i].X > maxXPosition || SnakeBoddy[i].Y >= maxYPosition
                            )
                        {
                            Die();
                        }

                        for (int J = 1; J < SnakeBoddy.Count; J++)
                        {
                            if (SnakeBoddy[i].X == SnakeBoddy[J].X && SnakeBoddy[i].Y == SnakeBoddy[J].Y)
                            {
                                Die();
                            }
                        }
                        for (int g = 0; g < SnakeBoddy2.Count; g++)
                        {
                            if (SnakeBoddy[0].X == SnakeBoddy2[g].X && SnakeBoddy[0].Y == SnakeBoddy2[g].Y)
                            {
                                Player1setting.GameScore += FoodSetting.food2;
                                label3.Text = Player1setting.GameScore.ToString();
                                Die();
                            }
                        }
                        if (SnakeFoods1.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
                        {
                            EatFood();
                        }

                        if (SnakeFoods2.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
                        {
                            EatFood2();
                        }

                        if (SnakeFoods3.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
                        {
                            EatFood3();
                        }

                        if (SnakeFoods4.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
                        {
                            EatFood4();
                        }
                    }
                    else
                    {
                        SnakeBoddy[i].X = SnakeBoddy[i - 1].X;
                        SnakeBoddy[i].Y = SnakeBoddy[i - 1].Y;
                    }
                }
            }
                
        }
        
        private void PlayerMovment2()
        {
            if (choice1.Checked)
            {
                for (int x = SnakeBoddy2.Count - 1; x >= 0; x--)
                {
                    if (x == 0)
                    {
                        switch (Player2setting.Movment2)
                        {
                            case SnakeMovment2.D:
                                SnakeBoddy2[x].X++;
                                break;

                            case SnakeMovment2.A:
                                SnakeBoddy2[x].X--;
                                break;

                            case SnakeMovment2.W:
                                SnakeBoddy2[x].Y--;
                                break;

                            case SnakeMovment2.S:
                                SnakeBoddy2[x].Y++;
                                break;
                        }

                        int maxXPosition2 = pictureBox1.Size.Width / Player2setting.Width2;
                        int maxYPosition2 = pictureBox1.Size.Height / Player2setting.Height2;

                        if (
                            SnakeBoddy2[x].X < 0 || SnakeBoddy2[x].Y < 0 ||
                            SnakeBoddy2[x].X > maxXPosition2 || SnakeBoddy2[x].Y >= maxYPosition2
                            )
                        {
                            Die2();
                        }

                        for (int h = 1; h < SnakeBoddy2.Count; h++)
                        {
                            if (SnakeBoddy2[x].X == SnakeBoddy2[h].X && SnakeBoddy2[x].Y == SnakeBoddy2[h].Y)
                            {
                                Die2();
                            }
                        }

                        for (int g = 0; g < SnakeBoddy.Count; g++)
                        {
                            if (SnakeBoddy2[0].X == SnakeBoddy[g].X && SnakeBoddy2[0].Y == SnakeBoddy[g].Y)
                            {
                                Player1setting.GameScore += FoodSetting.food2;
                                label2.Text = Player1setting.GameScore.ToString();
                                Die2();
                            }
                        }


                        if (SnakeFoods1.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
                        {
                            EatFood();
                        }

                        if (SnakeFoods2.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
                        {
                            EatFood2();
                        }

                        if (SnakeFoods3.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
                        {
                            EatFood3();
                        }

                        if (SnakeFoods4.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
                        {
                            EatFood4();
                        }
                    }
                    else
                    {
                        SnakeBoddy2[x].X = SnakeBoddy2[x - 1].X;
                        SnakeBoddy2[x].Y = SnakeBoddy2[x - 1].Y;
                    }
                }
            }
               

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void Keyisup(object sender, KeyEventArgs e)
        {
            KeyInput.SnakeDirections(e.KeyCode, false);
        }

        private void Keyisdown(object sender, KeyEventArgs e)
        {
            KeyInput.SnakeDirections(e.KeyCode, true);
        }

        private void UpdateGame(object sender, PaintEventArgs e)
        {

            Graphics canvas = e.Graphics;
            if (label1.Visible==false && Information.Visible==false)
            {
                foreach (Square SnakeFood in SnakeFoods1) {
                    canvas.FillRectangle(Brushes.Red,
                                       new Rectangle(
                                           SnakeFood.X * Player2setting.Width2,
                                           SnakeFood.Y * Player2setting.Width2,
                                           Player2setting.Width2, Player2setting.Height2
                                                  ));
                }
                foreach (Square SnakeFoods2 in SnakeFoods2) { 
                    canvas.FillRectangle(Brushes.Orange,
                                   new Rectangle(
                                       SnakeFoods2.X * Player1setting.Width,
                                       SnakeFoods2.Y * Player1setting.Width,
                                       Player1setting.Width, Player1setting.Height
                                              ));
                }
                foreach (Square SnakeFoods3 in SnakeFoods3) {
                    canvas.FillRectangle(Brushes.Purple,
                                   new Rectangle(
                                       SnakeFoods3.X * Player1setting.Width,
                                       SnakeFoods3.Y * Player1setting.Width,
                                       Player1setting.Width, Player1setting.Height
                                              ));
                }
                foreach (Square SnakeFoods4 in SnakeFoods4) {
                    canvas.FillRectangle(Brushes.Green,
                                   new Rectangle(
                                       SnakeFoods4.X * Player1setting.Width,
                                       SnakeFoods4.Y * Player1setting.Width,
                                       Player1setting.Width, Player1setting.Height
                                              ));
                }
            }
                

            if (choice2.Checked)
            {
                if (Player1setting.Gameover == false)
                {
                    Brush SnakeBoddyColor;

                    for (int i = 0; i < SnakeBoddy.Count; i++)
                    {
                        if (i == 0)
                        {
                            SnakeBoddyColor = Brushes.Black;
                            canvas.FillRectangle(SnakeBoddyColor,
                                               new Rectangle(
                                                   SnakeBoddy[i].X * Player1setting.Width,
                                                   SnakeBoddy[i].Y * Player1setting.Width,
                                                   Player1setting.Width, Player1setting.Height
                                                   ));
                        }
                        else
                        {
                            SnakeBoddyColor = Brushes.Red;
                            canvas.FillRectangle(SnakeBoddyColor,
                                               new Rectangle(
                                                   SnakeBoddy[i].X * Player1setting.Width,
                                                   SnakeBoddy[i].Y * Player1setting.Width,
                                                   Player1setting.Width, Player1setting.Height
                                                          ));
                        }
                    }
                }
            }
            if (choice1.Checked)
            {
                if (Player2setting.Gameover2 == false)
                {
                    Brush SnakeBoddyColor2;

                    for (int x = 0; x < SnakeBoddy2.Count; x++)
                    {
                        if (x == 0)
                        {
                            SnakeBoddyColor2 = Brushes.Black;
                            canvas.FillRectangle(SnakeBoddyColor2,
                                               new Rectangle(
                                                   SnakeBoddy2[x].X * Player2setting.Width2,
                                                   SnakeBoddy2[x].Y * Player2setting.Width2,
                                                   Player2setting.Width2, Player2setting.Height2
                                                   ));
                        }
                        else
                        {
                            SnakeBoddyColor2 = Brushes.Blue;
                            canvas.FillRectangle(SnakeBoddyColor2,
                                               new Rectangle(
                                                   SnakeBoddy2[x].X * Player2setting.Width2,
                                                   SnakeBoddy2[x].Y * Player2setting.Width2,
                                                   Player2setting.Width2, Player2setting.Height2
                                                          ));
                        }
                    }
                }
            }
               

            if (Player2setting.GameScore2 > Player1setting.GameScore && Player2setting.Gameover2 == true)
            {
                string gameEnd = "Player 1 n" + "With the score of " + Player2setting.GameScore2 + "nPress Q to play again n";
                label1.Text = gameEnd;
                label3.Visible = false;
                label2.Visible = false;
                label1.Visible = true;
            }
            if (Player2setting.GameScore2 < Player1setting.GameScore && Player1setting.Gameover == true) 
            {
                string gameEnd = "Player 2 n" + "With the score of " + Player1setting.GameScore + "nPress Q to play againn";
                label1.Text = gameEnd;
                label3.Visible = false;
                label2.Visible = false;
                label1.Visible = true;
            }
            if (Player2setting.GameScore2 == Player1setting.GameScore && Player1setting.Gameover == true && Player2setting.Gameover2 == true)
            {
                string gameEnd = "Draw nPress Q to play againn";
                label1.Text = gameEnd;
                label3.Visible = false;
                label2.Visible = false;
                label1.Visible = true;
            }

        }

        private void StartGame()
        {
            pictureBox1.Visible = false;
            Information.Visible = false;
            Player1Information.Visible = false;
            Player2Information.Visible = false;
            Player3Information.Visible = false;
            choice1.Visible = false;
            choice2.Visible = false;
            choice3.Visible = false;
            GameStart.Visible = false;
            pictureBox3.Visible = false;
            pictureBox1.Visible = true;
            label1.Visible = false;
            new FoodSetting();
            new Player1setting();
            new Player2setting();
            if (choice2.Checked)
            {
                label2.Visible = true;
                SnakeBoddy.Clear();
                Square SneakHead = new Square { X = 35, Y = 5 };
                SnakeBoddy.Add(SneakHead);
                if (choice2.Checked && !choice1.Checked)
                {
                    Square SneakHead2 = new Square { X = 35, Y = 5 };
                    Player2setting.GameScore2 = -1;
                    SnakeBoddy2.Add(SneakHead2);
                }
            }
            if (choice1.Checked)
            {
                label3.Visible = true;
                SnakeBoddy2.Clear();
                Square SneakHead2 = new Square { X = 10, Y = 5 };
                SnakeBoddy2.Add(SneakHead2);
                if (choice1.Checked && !choice2.Checked)
                {
                    Square SneakHead = new Square { X = 10, Y = 5 };
                    Player1setting.GameScore = -1;
                    SnakeBoddy.Add(SneakHead);
                }
            }
            label2.Text = Player1setting.GameScore.ToString();
            label3.Text = Player2setting.GameScore2.ToString();
            FoodEvent();

        }

        private void GenerateSnakeFood()
        {
            Square newFood = GenerateRandomFood();

            while (SnakeFoods1.Any(snakeFood => snakeFood.X == newFood.X && snakeFood.Y == newFood.Y))
            {
                newFood = GenerateRandomFood();
            }

            SnakeFoods1.Add(newFood);

        }

        private void GenerateSnakeFood2()
        {
            Square newFood = GenerateRandomFood();

            while (SnakeFoods2.Any(snakeFood => snakeFood.X == newFood.X && snakeFood.Y == newFood.Y))
            {
                newFood = GenerateRandomFood();
            }

            SnakeFoods2.Add(newFood);

        }

        private void GenerateSnakeFood3()
        {
            Square newFood = GenerateRandomFood();

            while (SnakeFoods3.Any(snakeFood => snakeFood.X == newFood.X && snakeFood.Y == newFood.Y))
            {
                newFood = GenerateRandomFood();
            }

            SnakeFoods3.Add(newFood);

        }

        private void GenerateSnakeFood4()
        {
            Square newFood = GenerateRandomFood();

            while (SnakeFoods4.Any(snakeFood => snakeFood.X == newFood.X && snakeFood.Y == newFood.Y))
            {
                newFood = GenerateRandomFood();
            }

            SnakeFoods4.Add(newFood);

        }

        private Square GenerateRandomFood()
        {
            int maxXPosition = pictureBox1.Size.Width / Player1setting.Width;
            int maxYPosition = pictureBox1.Size.Height / Player1setting.Height;

            return new Square { X = random.Next(0, maxXPosition), Y = random.Next(0, maxYPosition) };
        }


        private void EatFood()
        {
            if (SnakeFoods1.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
            {
                Square foodBody = new Square
                {
                    X = SnakeBoddy[SnakeBoddy.Count - 1].X,
                    Y = SnakeBoddy[SnakeBoddy.Count - 1].Y
                };

                SnakeBoddy.Add(foodBody);
                Player1setting.GameScore += FoodSetting.food1;
                label2.Text = Player1setting.GameScore.ToString();
                var snakePosition = SnakeBoddy[0];
                SnakeFoods1.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
            if (SnakeFoods1.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
            {
                Square foodBody2 = new Square
                {
                    X = SnakeBoddy2[SnakeBoddy2.Count - 1].X,
                    Y = SnakeBoddy2[SnakeBoddy2.Count - 1].Y
                };

                SnakeBoddy2.Add(foodBody2);
                Player2setting.GameScore2 += FoodSetting.food1;
                label3.Text = Player2setting.GameScore2.ToString();
                var snakePosition = SnakeBoddy2[0];
                SnakeFoods1.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
        }

        private void EatFood2()
        {
            if (SnakeFoods2.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
            {
                Square foodBody3 = new Square
                {
                    X = SnakeBoddy[SnakeBoddy.Count - 1].X,
                    Y = SnakeBoddy[SnakeBoddy.Count - 1].Y
                };

                SnakeBoddy.Add(foodBody3);

                Square foodBody35 = new Square
                {
                    X = SnakeBoddy[SnakeBoddy.Count - 1].X,
                    Y = SnakeBoddy[SnakeBoddy.Count - 1].Y
                };

                SnakeBoddy.Add(foodBody35);
                Player1setting.GameScore += FoodSetting.food2;
                label2.Text = Player1setting.GameScore.ToString();
                var snakePosition = SnakeBoddy[0];
                SnakeFoods2.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
            if (SnakeFoods2.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
            {
                Square foodBody4 = new Square
                {
                    X = SnakeBoddy2[SnakeBoddy2.Count - 1].X,
                    Y = SnakeBoddy2[SnakeBoddy2.Count - 1].Y
                };
                SnakeBoddy2.Add(foodBody4);

                Square foodBody45 = new Square
                {
                    X = SnakeBoddy2[SnakeBoddy2.Count - 1].X,
                    Y = SnakeBoddy2[SnakeBoddy2.Count - 1].Y
                };

                SnakeBoddy2.Add(foodBody45);
                Player2setting.GameScore2 += FoodSetting.food2;
                label3.Text = Player2setting.GameScore2.ToString();
                var snakePosition = SnakeBoddy2[0];
                SnakeFoods2.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
        }

        private void EatFood3()
        {
            if (SnakeFoods3.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
            {
                if (SnakeBoddy.Count > 1)
                {
                    SnakeBoddy.RemoveAt(SnakeBoddy.Count - 1);
                }
                Player1setting.GameScore += FoodSetting.food3;
                label2.Text = Player1setting.GameScore.ToString();
                var snakePosition = SnakeBoddy[0];
                SnakeFoods3.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
            if (SnakeFoods3.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
            {
                if (SnakeBoddy2.Count > 1)
                {
                    SnakeBoddy2.RemoveAt(SnakeBoddy2.Count - 1);
                }
                Player2setting.GameScore2 += FoodSetting.food3;
                label3.Text = Player2setting.GameScore2.ToString();
                var snakePosition = SnakeBoddy2[0];
                SnakeFoods3.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
            }
        }


        private void EatFood4()
        {
            if (SnakeFoods4.Any(s => s.X == SnakeBoddy[0].X && s.Y == SnakeBoddy[0].Y))
            {
                Player1setting.GameScore += FoodSetting.food4;
                label2.Text = Player1setting.GameScore.ToString();
                var snakePosition = SnakeBoddy[0];
                SnakeFoods4.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
                RandomEevent();
            }

            if (SnakeFoods4.Any(s => s.X == SnakeBoddy2[0].X && s.Y == SnakeBoddy2[0].Y))
            {
                Player2setting.GameScore2 += FoodSetting.food4;
                label3.Text = Player2setting.GameScore2.ToString();
                var snakePosition = SnakeBoddy2[0];
                SnakeFoods4.RemoveAll(s => s.X == snakePosition.X && s.Y == snakePosition.Y);
                RandomEevent();
            }
        }

        private void RandomEevent()
        {
            var time = DateTime.Now;
            eventTimer.Tick += new EventHandler(TimerEventProcessor);
            eventTimer.Interval = 3000;
            eventTimer.Start();
            while (exitFlag == false)
            {
                //Application.DoEvents();
                if ((DateTime.Now - time).TotalSeconds > 30)
                {
                    eventTimer.Stop();
                    exitFlag = true;
                }
            }
        }

        private void TimerEventProcessor(Object sender, EventArgs e)
        {
            eventTimer.Enabled = true;
            Player2setting.Movment2 = (SnakeMovment2)random.Next(4);
        }

        private void FoodEvent() 
        {
            foodTimer.Tick += new EventHandler(FoodGenerator);
            foodTimer.Interval = random.Next(1000,4000);
            foodTimer.Start();
            foodTimer2.Tick += new EventHandler(FoodGenerator2);
            foodTimer2.Interval = random.Next(4000, 7000);
            foodTimer2.Start();
            foodTimer3.Tick += new EventHandler(FoodGenerator3);
            foodTimer3.Interval = random.Next(5000, 10000);
            foodTimer3.Start();
            foodTimer4.Tick += new EventHandler(FoodGenerator4);
            foodTimer4.Interval = random.Next(3000, 6000);
            foodTimer4.Start();
        }

        private void FoodGenerator(object sender, EventArgs e)
        {
            foodTimer.Enabled = true;
            GenerateSnakeFood();
        }

        private void FoodGenerator2(object sender, EventArgs e)
        {
            foodTimer2.Enabled = true;
            GenerateSnakeFood2();
        }

        private void FoodGenerator3(object sender, EventArgs e)
        {
            foodTimer3.Enabled = true;
            GenerateSnakeFood3();
        }

        private void FoodGenerator4(object sender, EventArgs e)
        {
            foodTimer4.Enabled = true;
            GenerateSnakeFood4();
        }

        private void Die()
        {
            Player1setting.Gameover = true;
        }

        private void Die2()
        {
            Player2setting.Gameover2 = true;
        }

        private void GameStart_Click(object sender, EventArgs e)
        {
            if (choice1.Checked || choice2.Checked || choice3.Checked) {
                StartGame();
            }

        }
    }
}

KeyInput.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Windows.Forms;

namespace snakeGame3players
{
    class KeyInput
    {

        private static Hashtable keyTable = new Hashtable();

        public static bool KeyInputs(Keys key)
        {
            if (keyTable[key] == null)
            {
                return false;
            }

            return (bool)keyTable[key];
        }

        public static void SnakeDirections(Keys key, bool direction)
        {
            keyTable[key] = direction;
        }

    }
}

Player1setting.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace snakeGame3players
{
    public enum SnakeMovment
    {
        Right,
        Left,
        Down,
        Up
    };

    public enum SnakeMovment2
    {
        W,
        S,
        D,
        A
    };


    class Player1setting
    {
        public static int Width { get; set; }
        public static int Height { get; set; }
        public static int GameScore { get; set; }
        public static bool Gameover { get; set; }
        public static SnakeMovment Movment { get; set; }

        public Player1setting()
        {
            Height = 16;
            Width = 16;
            Gameover = false;
            Movment = SnakeMovment.Down;
            GameScore = 0;
        }

    }

    class Player2setting
    {
        public static int Width2 { get; set; }
        public static int Height2 { get; set; }
        public static int GameScore2 { get; set; }
        public static bool Gameover2 { get; set; }
        public static SnakeMovment2 Movment2 { get; set; }

        public Player2setting()
        {
            Height2 = 16;
            Width2 = 16;
            Gameover2 = false;
            Movment2 = SnakeMovment2.S;
            GameScore2 = 0;
        }

    }
    class FoodSetting
    {
        public static int food1 { get; set; }
        public static int food2 { get; set; }
        public static int food3 { get; set; }
        public static int food4 { get; set; }

        public FoodSetting()
        {
            food1 = 1;
            food2 = 5;
            food3 = 1;
            food4 = 1;
        }
    }

}

Square.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace snakeGame3players
{
    class Square
    {
        public int X { get; set; }
        public int Y { get; set; }

        public Square()
        {
            X = 0;
            Y = 0;
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace snakeGame3players
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}


Get this bounty!!!

#StackBounty: #javascript #object-oriented #array #ecmascript-6 #iteration JavaScript: A drawing program in OOP

Bounty: 50

I made a little drawing program in OOP.

The game consists of a canvas which is basically a 2d array where the user can draw various shapes on it. There can be multiple shapes overlapped on top of each other at the same spot. The shape that was drawn the last would appear on the top of the canvas and that is what gets printed out when we print the canvas – only those shapes at the top would be printed out.

Here I have two classes Rectangle and Canvas


class Rectangle {
  constructor(positions) {
    this.positions = positions
    this.character = Symbol()
  }

  move(dx, dy) {
    for (const position of this.positions) {
      const nx = position[0] + dx
      const ny = position[1] + dy
      position[0] = nx
      position[1] = ny
    }
  }
}

class Canvas {
  constructor(size) {
    this.canvas = Array.from({ length: size }, () =>
      Array.from({ length: size }, () => null)
    )
  }

  draw(rectangle) {
    const { positions, character } = rectangle
    for (const [row, col] of positions) {
      if (this.canvas[row][col] === null) this.canvas[row][col] = [character]
      else this.canvas[row][col].push(character)
    }
  }

  remove(rectangle) {
    const { positions, character } = rectangle
    if (positions.length === 0) return
    for (const position of positions) {
      const list = this.canvas[position[0]][position[1]]
      list.splice(list.indexOf(character), 1)
    }
  }

  print(palette) {
    let rows = ''

    for (let i = 0; i < this.canvas.length; i++) {
      for (let j = 0; j < this.canvas[i].length; j++) {
        if (this.canvas[i][j] === null || this.canvas[i][j].length === 0) {
          rows += '*' // empty
        } else {
          rows += palette[this.canvas[i][j][this.canvas[i][j].length - 1]]
        }
      }

      rows += 'n'
    }
    console.log(rows)
  }

  moveToTop(rectangle) {
    this.remove(rectangle)
    this.draw(rectangle)
  }

  drag(startPosition, endPosition, map) {
    const list = this.canvas[startPosition[0]][startPosition[1]]
    if (list?.length === 0) return

    const character = list[list.length - 1]

    const rectangle = map[character]
    const dx = endPosition[0] - startPosition[0]
    const dy = endPosition[1] - startPosition[1]
    this.remove(rectangle)
    rectangle.move(dx, dy)
    this.draw(rectangle)
  }
}

And I have a map that maps the Rectangle‘s character to that Rectangle instance. and another object called palette and tells the canvas how that shape or pixel would look like when being printed (initially palette is part of canvas but I feel like I should decouple them so canvas only holds the state of the current canvas and is not opinionated on how the shapes should look like when they are printed out)

const rectangle1 = new Rectangle(
  [
    [0, 1],
    [1, 2],
    [0, 2],
  ]
)
const rectangle2 = new Rectangle(
  [
    [0, 0],
    [0, 1],
    [1, 0],
  ]
)
const rectangle3 = new Rectangle([[0, 0]])


const palette = {
  [rectangle1.character]: 'A',
  [rectangle2.character]: 'B',
  [rectangle3.character]: 'C',
}

const map = {
  [rectangle1.character]: rectangle1,
  [rectangle2.character]: rectangle2,
  [rectangle3.character]: rectangle3,
}

here is the link to a live demo

Please feel free to give me any feedback. Specifically, I would love to know:

  1. if there is a better to approach the OO design here? Did I do a good job at separating the concerns here? Does every class have the methods here?
  2. I think I need some help to refactor the map and palette. Specifically map, it is manually constructed right now. I am not sure if I need to create two classes for them.
  3. Is there any alternatives to approach implementing such a canvas? What are some of the pros and cons?


Get this bounty!!!

#StackBounty: #javascript #object-oriented JavaScript: A drawing program in OOP

Bounty: 50

I made a little drawing program in OOP.

The game consists of a canvas which is basically a 2d array where the user can draw various shapes on it. There can be multiple shapes overlapped on top of each other at the same spot. The shape that was drawn the last would appear on the top of the canvas and that is what gets printed out when we print the canvas – only those shapes at the top would be printed out.

Here I have two classes Rectangle and Canvas


class Rectangle {
  constructor(positions) {
    this.positions = positions
    this.character = Symbol()
  }
```


Get this bounty!!!

#StackBounty: #javascript #object-oriented #game #functional-programming #2048 JavaScript OOD: 2048

Bounty: 50

I wrote a 2048 game in JavaScript with an object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

Here is the implementation:

class Game {
  SIZE = 4
  constructor() {
    this.board = Array.from({ length: this.SIZE * this.SIZE }, () => 0).reduce(
      (arrays, curr) => {
        const lastArray = arrays[arrays.length - 1]
        if (lastArray.length < this.SIZE) lastArray.push(curr)
        else arrays.push([curr])
        return arrays
      },
      [[]]
    )
    this.isWin = false
    this._init()
  }

  _init() {
    const pickedTiles = this._randomlyPick(2)
    for (const [row, col] of pickedTiles) {
      this.board[row][col] = Game.generateTile()
    }
  }

  static generateTile() {
    if (Math.random() > 0.5) return 2
    return 4
  }

  _getEmptyTiles() {
    const emptyTiles = []
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (this.board[row][col] === 0) emptyTiles.push([col, row])
      }
    }
    return emptyTiles
  }

  _randomlyPick(numOfItems) {
    const emptyTiles = this._getEmptyTiles()
    for (let i = 0; i < numOfItems; i++) {
      const toSwap = i + Math.floor(Math.random() * (emptyTiles.length - i))
      ;[emptyTiles[i], emptyTiles[toSwap]] = [emptyTiles[toSwap], emptyTiles[i]]
    }
    return emptyTiles.slice(0, numOfItems)
  }

  spawn() {
    // randomly spawn empty tiles with 2 or 4
    const [emtpyTile] = this._randomlyPick(1)
    this.board[emtpyTile[0]][emtpyTile[1]] = Game.generateTile()
  }

  play(dir) {
    if (this.canPlay()) {
      switch (dir) {
        case Game.moveUp:
          this._mergeUp()
          break
        case Game.moveRight:
          this._mergeRight()
          break
        case Game.moveLeft:
          this._mergeLeft()
          break
        case Game.moveDown:
          this._mergeDown()
          break
      }
      this.spawn()

      return true
    }
    return false
  }
  checkIsWin() {
    return this.isWin
  }

  static peek(array) {
    return array[array.length - 1]
  }

  static zip(arrays) {
    const result = []
    for (let i = 0; i < arrays[0].length; ++i) {
      result.push(arrays.map((array) => array[i]))
    }
    return result
  }

  _mergeRowRight(sparseRow) {
    const row = sparseRow.filter((x) => x !== 0)
    const result = []
    while (row.length) {
      let value = row.pop()
      if (Game.peek(row) === value) value += row.pop()
      result.unshift(value)
    }

    while (result.length < 4) result.unshift(0)
    return result
  }

  _mergeRowLeft(row) {
    return this._mergeRowRight([...row].reverse()).reverse()
  }

  _mergeUp() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRowLeft(row)))
  }
  _mergeDown() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRight(row)))
  }

  _mergeRight() {
    this.board = this.board.map((row) => this._mergeRowRight(row))
  }
  _mergeLeft() {
    this.board = this.board.map((row) => this._mergeRowLeft(row))
  }

  canPlay() {
    const dirs = [
      [0, 1],
      [1, 0],
      [-1, 0],
      [0, -1],
    ]
    const visited = new Set()
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (visited.has([row, col].toString())) continue
        const tile = this.board[row][col]
        if (tile === 2048) {
          this.isWin = true
          return false
        }
        if (tile === 0) return true
        for (const [dx, dy] of dirs) {
          if (this.board[row + dx]?.[col + dy] === tile) return true
        }
        visited.add([row, col].toString())
      }
    }
    return false
  }
}

Game.moveUp = Symbol('moveUp')
Game.moveDown = Symbol('moveUp')
Game.moveLeft = Symbol('moveUp')
Game.moveRight = Symbol('moveUp')

const game = new Game()
console.log(game.board);
game.play(Game.moveUp)
console.log(game.board);
game.play(Game.moveRight)
console.log(game.board);

Any feedback is welcomed. Specifically, I would like to know:

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal?
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.


Get this bounty!!!

#StackBounty: #javascript #object-oriented #game #2048 JavaScript OOD: 2048

Bounty: 50

I wrote a 2048 game in JavaScript with a object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

Here is the implementation:

class Game {
  SIZE = 4
  constructor() {
    this.board = Array.from({ length: this.SIZE * this.SIZE }, () => 0).reduce(
      (arrays, curr) => {
        const lastArray = arrays[arrays.length - 1]
        if (lastArray.length < this.SIZE) lastArray.push(curr)
        else arrays.push([curr])
        return arrays
      },
      [[]]
    )
    this.isWin = false
    this._init()
  }

  _init() {
    const pickedTiles = this._randomlyPick(2)
    for (const [row, col] of pickedTiles) {
      this.board[row][col] = Game.generateTile()
    }
  }

  static generateTile() {
    if (Math.random() > 0.5) return 2
    return 4
  }

  _getEmptyTiles() {
    const emptyTiles = []
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (this.board[row][col] === 0) emptyTiles.push([col, row])
      }
    }
    return emptyTiles
  }

  _randomlyPick(numOfItems) {
    const emptyTiles = this._getEmptyTiles()
    for (let i = 0; i < numOfItems; i++) {
      const toSwap = i + Math.floor(Math.random() * (emptyTiles.length - i))
      ;[emptyTiles[i], emptyTiles[toSwap]] = [emptyTiles[toSwap], emptyTiles[i]]
    }
    return emptyTiles.slice(0, numOfItems)
  }

  spawn() {
    // randomly spawn empty tiles with 2 or 4
    const [emtpyTile] = this._randomlyPick(1)
    this.board[emtpyTile[0]][emtpyTile[1]] = Game.generateTile()
  }

  play(dir) {
    if (this.canPlay()) {
      switch (dir) {
        case Game.moveUp:
          this._mergeUp()
          break
        case Game.moveRight:
          this._mergeRight()
          break
        case Game.moveLeft:
          this._mergeLeft()
          break
        case Game.moveDown:
          this._mergeDown()
          break
      }
      this.spawn()

      return true
    }
    return false
  }
  checkIsWin() {
    return this.isWin
  }

  static peek(array) {
    return array[array.length - 1]
  }

  static zip(arrays) {
    const result = []
    for (let i = 0; i < arrays[0].length; ++i) {
      result.push(arrays.map((array) => array[i]))
    }
    return result
  }

  _mergeRowRight(sparseRow) {
    const row = sparseRow.filter((x) => x !== 0)
    const result = []
    while (row.length) {
      let value = row.pop()
      if (Game.peek(row) === value) value += row.pop()
      result.unshift(value)
    }

    while (result.length < 4) result.unshift(0)
    return result
  }

  _mergeRowLeft(row) {
    return this._mergeRowRight([...row].reverse()).reverse()
  }

  _mergeUp() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRowLeft(row)))
  }
  _mergeDown() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRight(row)))
  }

  _mergeRight() {
    this.board = this.board.map((row) => this._mergeRowRight(row))
  }
  _mergeLeft() {
    this.board = this.board.map((row) => this._mergeRowLeft(row))
  }

  canPlay() {
    const dirs = [
      [0, 1],
      [1, 0],
      [-1, 0],
      [0, -1],
    ]
    const visited = new Set()
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (visited.has([row, col].toString())) continue
        const tile = this.board[row][col]
        if (tile === 2048) {
          this.isWin = true
          return false
        }
        if (tile === 0) return true
        for (const [dx, dy] of dirs) {
          if (this.board[row + dx]?.[col + dy] === tile) return true
        }
        visited.add([row, col].toString())
      }
    }
    return false
  }
}

Game.moveUp = Symbol('moveUp')
Game.moveDown = Symbol('moveUp')
Game.moveLeft = Symbol('moveUp')
Game.moveRight = Symbol('moveUp')

const game = new Game()
console.log(game.board);
game.play(Game.moveUp)
console.log(game.board);
game.play(Game.moveRight)
console.log(game.board);

Any feedback is welcomed. Specifically, I would like to know:

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal.
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.


Get this bounty!!!

#StackBounty: #object-oriented #matrix #r #macros Was this an idiomatic and prudent way to extend R's matrix multiplication syntax?…

Bounty: 50

After reading one too many Lisp books, I decided to try extending R’s syntax. My goal was to implement repeated matrix multiplication in a way such that I could write matrix%^%n to produce the result from multiplying matrix by itself n times (where n is a natural number). I produced the following working code that gives the expected outputs, but found it unsatisfactory.

`%^%`<-function(squareMat,exponent)
{
  stopifnot(is.matrix(squareMat),is.numeric(exponent),exponent%%1==0)
  out<-diag(nrow = nrow(squareMat))
  for(i in seq_len(exponent)){out<-squareMat%*%out}
  out
}
testMatrix<-matrix(c(0,10,20,30),2,2)
lapply(0:3,function(k) testMatrix%^%k)#tests

For the purposes of this question, I’m happy to ignore the contents of my for loop. I know that it’s not optimised. What I’m more interested in investigating is if this was a prudent way to extend R’s syntax. I have listed my objection to my code below. If they are valid, how can they be addressed?

  1. The method for checking if exponent is an integer is pathetic. I don’t think that doing better is possible, but I would dearly love to be proven wrong.
  2. My strongest objection is that I believe that this problem calls for an S3 or S4 solution, but I do not believe that one is possible. isS4(testMatrix) tells me that matrices are not S4 objects, ruling that out. S3 might be possible, but I’m unsure if this will make problem #1 even worse (because S3 is not very strict on types) or requiring risky hacking of the matrix type.
  3. Because of problem #1, stopifnot does not throw useful error messages.
  4. The job of throwing errors regarding the multiplication is delegated to %*%. I’m OK with that, but I don’t know if I should be.
  5. Again, ignoring the content of the for loop, was for actually the right thing to use? Is there something like an apply family function for "the index of my loop doesn’t actually matter, I just want to run this code seq_len(exponent) times"?


Get this bounty!!!

#StackBounty: #object-oriented #matrix #r #macros #amazon-web-services Was this an idiomatic and prudent way to extend R's matrix m…

Bounty: 50

After reading one too many Lisp books, I decided to try extending R’s syntax. My goal was to implement repeated matrix multiplication in a way such that I could write matrix%^%n to produce the result from multiplying matrix by itself n times (where n is a natural number). I produced the following working code that gives the expected outputs, but found it unsatisfactory.

`%^%`<-function(squareMat,exponent)
{
  stopifnot(is.matrix(squareMat),is.numeric(exponent),exponent%%1==0)
  out<-diag(nrow = nrow(squareMat))
  for(i in seq_len(exponent)){out<-squareMat%*%out}
  out
}
testMatrix<-matrix(c(0,10,20,30),2,2)
lapply(0:3,function(k) testMatrix%^%k)#tests

For the purposes of this question, I’m happy to ignore the contents of my for loop. I know that it’s not optimised. What I’m more interested in investigating is if this was a prudent way to extend R’s syntax. I have listed my objection to my code below. If they are valid, how can they be addressed?

  1. The method for checking if exponent is an integer is pathetic. I don’t think that doing better is possible, but I would dearly love to be proven wrong.
  2. My strongest objection is that I believe that this problem calls for an S3 or S4 solution, but I do not believe that one is possible. isS4(testMatrix) tells me that matrices are not S4 objects, ruling that out. S3 might be possible, but I’m unsure if this will make problem #1 even worse (because S3 is not very strict on types) or requiring risky hacking of the matrix type.
  3. Because of problem #1, stopifnot does not throw useful error messages.
  4. The job of throwing errors regarding the multiplication is delegated to %*%. I’m OK with that, but I don’t know if I should be.
  5. Again, ignoring the content of the for loop, was for actually the right thing to use? Is there something like an apply family function for "the index of my loop doesn’t actually matter, I just want to run this code seq_len(exponent) times"?


Get this bounty!!!