コード例 #1
0
ファイル: TIA.cs プロジェクト: timconner/BizHawk
        // Execute TIA cycles
        public void Execute(int cycles)
        {
            // Still ignoring cycles...

            // delay vblank latch
            if (_vblankDelay > 0)
            {
                _vblankDelay++;
                if (_vblankDelay == 3)
                {
                    _vblankEnabled = (_vblankValue & 0x02) != 0;
                    _vblankDelay   = 0;
                }
            }

            // delay latch to new playfield register
            if (_pf0Updater)
            {
                _pf0DelayClock++;
                if (_pf0DelayClock > _pf0MaxDelay)
                {
                    _playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(_pf0Update, 8) & 0x0F) << 16));
                    _pf0Updater    = false;
                }
            }

            if (_pf1Updater)
            {
                _pf1DelayClock++;
                if (_pf1DelayClock > _pf1MaxDelay)
                {
                    _playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (_pf1Update << 8));
                    _pf1Updater    = false;
                }
            }

            if (_pf2Updater)
            {
                _pf2DelayClock++;
                if (_pf2DelayClock > _pf2MaxDelay)
                {
                    _playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(_pf2Update, 8));
                    _pf2Updater    = false;
                }
            }

            // delay latch to missile enable
            if (_enam0Delay > 0)
            {
                _enam0Delay++;
                if (_enam0Delay == 3)
                {
                    _enam0Delay = 0;
                    _player0.Missile.Enabled = _enam0Val;
                }
            }

            if (_enam1Delay > 0)
            {
                _enam1Delay++;
                if (_enam1Delay == 3)
                {
                    _enam1Delay = 0;
                    _player1.Missile.Enabled = _enam1Val;
                }
            }

            // delay latch to ball enable
            if (_enambDelay > 0)
            {
                _enambDelay++;
                if (_enambDelay == 3)
                {
                    _enambDelay   = 0;
                    _ball.Enabled = _enambVal;
                }
            }

            // delay latch to player graphics registers
            if (_prg0Delay > 0)
            {
                _prg0Delay++;
                if (_prg0Delay == 3)
                {
                    _prg0Delay    = 0;
                    _player0.Grp  = _prg0Val;
                    _player1.Dgrp = _player1.Grp;
                }
            }

            if (_prg1Delay > 0)
            {
                _prg1Delay++;
                if (_prg1Delay == 3)
                {
                    _prg1Delay    = 0;
                    _player1.Grp  = _prg1Val;
                    _player0.Dgrp = _player0.Grp;

                    // TODO: Find a game that uses this functionality and test it
                    _ball.Denabled = _ball.Enabled;
                }
            }

            // HMP write delay
            if (_hmp0Delay > 0)
            {
                _hmp0Delay++;
                if (_hmp0Delay == 4)
                {
                    _hmp0Delay  = 0;
                    _player0.HM = _hmp0Val;
                }
            }

            if (_hmp1Delay > 0)
            {
                _hmp1Delay++;
                if (_hmp1Delay == 3)
                {
                    _hmp1Delay  = 0;
                    _player1.HM = _hmp1Val;
                }
            }

            // Reset the RDY flag when we reach hblank
            if (_hsyncCnt <= 0)
            {
                _core.Cpu.RDY = true;
            }

            // Assume we're on the left side of the screen for now
            var rightSide = false;

            // ---- Things that happen only in the drawing section ----
            // TODO: Remove this magic number (17). It depends on the HMOVE
            if (_hsyncCnt >= (_hmove.LateHBlankReset ? 76 : 68))
            {
                _doTicks = false;

                // TODO: Remove this magic number
                if ((_hsyncCnt / 4) >= 37)
                {
                    rightSide = true;
                }

                // The bit number of the PF data which we want
                int pfBit = ((_hsyncCnt / 4) - 17) % 20;

                // Create the mask for the bit we want
                // Note that bits are arranged 0 1 2 3 4 .. 19
                int pfMask = 1 << (20 - 1 - pfBit);

                // Reverse the mask if on the right and playfield is reflected
                if (rightSide && _playField.Reflect)
                {
                    pfMask = ReverseBits(pfMask, 20);
                }

                // Calculate collisions
                byte collisions = 0x00;

                if ((_playField.Grp & pfMask) != 0)
                {
                    collisions |= CXPF;
                }

                // ---- Player 0 ----
                collisions |= _player0.Tick() ? CXP0 : (byte)0x00;

                // ---- Missile 0 ----
                collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00;

                // ---- Player 1 ----
                collisions |= _player1.Tick() ? CXP1 : (byte)0x00;

                // ---- Missile 0 ----
                collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00;

                // ---- Ball ----
                collisions |= _ball.Tick() ? CXBL : (byte)0x00;

                // Pick the pixel color from collisions
                int pixelColor = BackColor;
                if (_core.Settings.ShowBG)
                {
                    pixelColor = _palette[_playField.BkColor];
                }

                if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
                {
                    if (_playField.Score)
                    {
                        pixelColor = !rightSide
                                                        ? _palette[_player0.Color]
                                                        : _palette[_player1.Color];
                    }
                    else
                    {
                        pixelColor = _palette[_playField.PfColor];
                    }
                }

                if ((collisions & CXBL) != 0)
                {
                    _ball.Collisions |= collisions;
                    if (_core.Settings.ShowBall)
                    {
                        pixelColor = _palette[_playField.PfColor];
                    }
                }

                if ((collisions & CXM1) != 0)
                {
                    _player1.Missile.Collisions |= collisions;
                    if (_core.Settings.ShowMissle2)
                    {
                        pixelColor = _palette[_player1.Color];
                    }
                }

                if ((collisions & CXP1) != 0)
                {
                    _player1.Collisions |= collisions;
                    if (_core.Settings.ShowPlayer2)
                    {
                        pixelColor = _palette[_player1.Color];
                    }
                }

                if ((collisions & CXM0) != 0)
                {
                    _player0.Missile.Collisions |= collisions;
                    if (_core.Settings.ShowMissle1)
                    {
                        pixelColor = _palette[_player0.Color];
                    }
                }

                if ((collisions & CXP0) != 0)
                {
                    _player0.Collisions |= collisions;
                    if (_core.Settings.ShowPlayer1)
                    {
                        pixelColor = _palette[_player0.Color];
                    }
                }

                if (_playField.Score && !_playField.Priority && ((collisions & CXPF) != 0) && _core.Settings.ShowPlayfield)
                {
                    pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color];
                }

                if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
                {
                    pixelColor = _palette[_playField.PfColor];
                }

                // Handle vblank
                if (_vblankEnabled)
                {
                    pixelColor = BackColor;
                }

                // Add the pixel to the scanline
                // TODO: Remove this magic number (68)
                int y = _currentScanLine;

                // y >= max screen height means lag frame or game crashed, but is a legal situation.
                // either way, there's nothing to display
                if (y < MaxScreenHeight)
                {
                    int x = _hsyncCnt - 68;
                    if (x < 0 || x > 159)                     // this can't happen, right?
                    {
                        throw new Exception();                // TODO
                    }

                    _scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor;
                }
            }
            else
            {
                _doTicks = true;
            }

            // if extended HBLank is active, the screen area still needs a color
            if (_hsyncCnt >= 68 && _hsyncCnt < 76 && _hmove.LateHBlankReset)
            {
                int pixelColor = 0;

                // Add the pixel to the scanline
                // TODO: Remove this magic number (68)
                int y = _currentScanLine;

                // y >= max screen height means lag frame or game crashed, but is a legal situation.
                // either way, there's nothing to display
                if (y < MaxScreenHeight)
                {
                    int x = _hsyncCnt - 68;
                    if (x < 0 || x > 159)                     // this can't happen, right?
                    {
                        throw new Exception();                // TODO
                    }

                    _scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor;
                }
            }

            // Handle HMOVE
            if (_hmove.HMoveEnabled)
            {
                if (_hmove.DecCntEnabled)
                {
                    // Actually do stuff only evey 4 pulses
                    if (_hmove.HMoveCnt == 0)
                    {
                        // If the latch is still set
                        if (_hmove.Player0Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                _p0Stuff = true;
                            }
                            else
                            {
                                _hmove.Player0Latch = false;
                            }
                        }

                        if (_hmove.Missile0Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
                            {
                                _m0Stuff = true;
                            }
                            else
                            {
                                _hmove.Missile0Latch = false;
                            }
                        }

                        if (_hmove.Player1Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                _p1Stuff = true;
                            }
                            else
                            {
                                _hmove.Player1Latch = false;
                            }
                        }

                        if (_hmove.Missile1Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
                            {
                                _m1Stuff = true;
                            }
                            else
                            {
                                _hmove.Missile1Latch = false;
                            }
                        }

                        if (_hmove.BallLatch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                _bStuff = true;
                            }
                            else
                            {
                                _hmove.BallLatch = false;
                            }
                        }

                        if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch)
                        {
                            _hmove.HMoveEnabled  = false;
                            _hmove.DecCntEnabled = false;
                            _hmove.HMoveDelayCnt = 0;
                        }
                    }

                    _hmove.HMoveCnt++;
                    _hmove.HMoveCnt %= 4;

                    if (_p0Stuff && _hsyncCnt % 4 == 0)
                    {
                        _p0Stuff = false;

                        // "Clock-Stuffing"
                        if (_doTicks)
                        {
                            _player0.Tick();
                        }

                        // Increase by 1, max of 15
                        _hmove.test_count_p0++;
                        if (_hmove.test_count_p0 < 16)
                        {
                            _hmove.Player0Cnt++;
                        }
                        else
                        {
                            _hmove.Player0Cnt = 0;
                        }
                    }

                    if (_p1Stuff && _hsyncCnt % 4 == 0)
                    {
                        _p1Stuff = false;

                        // "Clock-Stuffing"
                        if (_doTicks)
                        {
                            _player1.Tick();
                        }

                        // Increase by 1, max of 15
                        _hmove.test_count_p1++;
                        if (_hmove.test_count_p1 < 16)
                        {
                            _hmove.Player1Cnt++;
                        }
                        else
                        {
                            _hmove.Player1Cnt = 0;
                        }
                    }

                    if (_m0Stuff && _hsyncCnt % 4 == 0)
                    {
                        _m0Stuff = false;

                        // "Clock-Stuffing"
                        if (_doTicks)
                        {
                            _player0.Missile.Tick();
                        }

                        // Increase by 1, max of 15
                        _hmove.test_count_m0++;
                        if (_hmove.test_count_m0 < 16)
                        {
                            _hmove.Missile0Cnt++;
                        }
                        else
                        {
                            _hmove.Missile0Cnt = 0;
                        }
                    }

                    if (_m1Stuff && _hsyncCnt % 4 == 0)
                    {
                        _m1Stuff = false;

                        // "Clock-Stuffing"
                        if (_doTicks)
                        {
                            _player1.Missile.Tick();
                        }

                        // Increase by 1, max of 15
                        _hmove.test_count_m1++;
                        if (_hmove.test_count_m1 < 16)
                        {
                            _hmove.Missile1Cnt++;
                        }
                        else
                        {
                            _hmove.Missile1Cnt = 0;
                        }
                    }

                    if (_bStuff && _hsyncCnt % 4 == 0)
                    {
                        _bStuff = false;

                        // "Clock-Stuffing"
                        if (_doTicks)
                        {
                            _ball.Tick();
                        }

                        // Increase by 1, max of 15
                        _hmove.test_count_b++;
                        if (_hmove.test_count_b < 16)
                        {
                            _hmove.BallCnt++;
                        }
                        else
                        {
                            _hmove.BallCnt = 0;
                        }
                    }
                }

                if (_hmove.HMoveDelayCnt < 5)
                {
                    _hmove.HMoveDelayCnt++;
                }

                if (_hmove.HMoveDelayCnt == 5)
                {
                    _hmove.HMoveDelayCnt++;
                    _hmove.HMoveCnt      = 0;
                    _hmove.DecCntEnabled = true;

                    _hmove.test_count_p0 = 0;
                    _hmove.test_count_p1 = 0;
                    _hmove.test_count_m0 = 0;
                    _hmove.test_count_m1 = 0;
                    _hmove.test_count_b  = 0;

                    _hmove.Player0Latch = true;
                    _hmove.Player0Cnt   = 0;

                    _hmove.Missile0Latch = true;
                    _hmove.Missile0Cnt   = 0;

                    _hmove.Player1Latch = true;
                    _hmove.Player1Cnt   = 0;

                    _hmove.Missile1Latch = true;
                    _hmove.Missile1Cnt   = 0;

                    _hmove.BallLatch = true;
                    _hmove.BallCnt   = 0;

                    _hmove.LateHBlankReset = true;
                }
            }

            // do the audio sampling
            if (_hsyncCnt == 36 || _hsyncCnt == 148)
            {
                LocalAudioCycles[AudioClocks] += (short)(AUD[0].Cycle() / 2);
                LocalAudioCycles[AudioClocks] += (short)(AUD[1].Cycle() / 2);
                AudioClocks++;
            }

            // Increment the hsync counter
            _hsyncCnt++;
            _hsyncCnt %= 228;

            // End of the line? Add it to the buffer!
            if (_hsyncCnt == 0)
            {
                _hmove.LateHBlankReset = false;
                _currentScanLine++;
                LineCount++;
            }
        }
コード例 #2
0
        // Execute TIA cycles
        public void Execute(int cycles)
        {
            // Still ignoring cycles...

            // Assume we're on the left side of the screen for now
            var rightSide = false;

            // ---- Things that happen only in the drawing section ----
            // TODO: Remove this magic number (17). It depends on the HMOVE
            if ((_hsyncCnt / 4) >= (_hmove.LateHBlankReset ? 19 : 17))
            {
                // TODO: Remove this magic number
                if ((_hsyncCnt / 4) >= 37)
                {
                    rightSide = true;
                }

                // The bit number of the PF data which we want
                int pfBit = ((_hsyncCnt / 4) - 17) % 20;

                // Create the mask for the bit we want
                // Note that bits are arranged 0 1 2 3 4 .. 19
                int pfMask = 1 << (20 - 1 - pfBit);

                // Reverse the mask if on the right and playfield is reflected
                if (rightSide && _playField.Reflect)
                {
                    pfMask = ReverseBits(pfMask, 20);
                }

                // Calculate collisions
                byte collisions = 0x00;

                if ((_playField.Grp & pfMask) != 0)
                {
                    collisions |= CXPF;
                }


                // ---- Player 0 ----
                collisions |= _player0.Tick() ? CXP0 : (byte)0x00;

                // ---- Missile 0 ----
                collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00;

                // ---- Player 1 ----
                collisions |= _player1.Tick() ? CXP1 : (byte)0x00;

                // ---- Missile 0 ----
                collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00;

                // ---- Ball ----
                collisions |= _ball.Tick() ? CXBL : (byte)0x00;


                // Pick the pixel color from collisions
                int pixelColor = BackColor;
                if (_core.Settings.ShowBG)
                {
                    pixelColor = _palette[_playField.BkColor];
                }

                if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
                {
                    if (_playField.Score)
                    {
                        if (!rightSide)
                        {
                            pixelColor = _palette[_player0.Color];
                        }
                        else
                        {
                            pixelColor = _palette[_player1.Color];
                        }
                    }
                    else
                    {
                        pixelColor = _palette[_playField.PfColor];
                    }
                }

                if ((collisions & CXBL) != 0)
                {
                    _ball.Collisions |= collisions;
                    if (_core.Settings.ShowBall)
                    {
                        pixelColor = _palette[_playField.PfColor];
                    }
                }

                if ((collisions & CXM1) != 0)
                {
                    _player1.Missile.Collisions |= collisions;
                    if (_core.Settings.ShowMissle2)
                    {
                        pixelColor = _palette[_player1.Color];
                    }
                }

                if ((collisions & CXP1) != 0)
                {
                    _player1.Collisions |= collisions;
                    if (_core.Settings.ShowPlayer2)
                    {
                        pixelColor = _palette[_player1.Color];
                    }
                }

                if ((collisions & CXM0) != 0)
                {
                    _player0.Missile.Collisions |= collisions;
                    if (_core.Settings.ShowMissle1)
                    {
                        pixelColor = _palette[_player0.Color];
                    }
                }

                if ((collisions & CXP0) != 0)
                {
                    _player0.Collisions |= collisions;
                    if (_core.Settings.ShowPlayer1)
                    {
                        pixelColor = _palette[_player0.Color];
                    }
                }

                if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
                {
                    if (_playField.Score)
                    {
                        pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color];
                    }
                    else
                    {
                        pixelColor = _palette[_playField.PfColor];
                    }
                }

                // Handle vblank
                if (_vblankEnabled)
                {
                    pixelColor = BackColor;
                }

                // Add the pixel to the scanline
                // TODO: Remove this magic number (68)

                int y = _CurrentScanLine;
                // y >= max screen height means lag frame or game crashed, but is a legal situation.
                // either way, there's nothing to display
                if (y < MaxScreenHeight)
                {
                    int x = _hsyncCnt - 68;
                    if (x < 0 || x > 159)                     // this can't happen, right?
                    {
                        throw new Exception();                // TODO
                    }
                    _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor;
                }
            }

            // ---- Things that happen every time ----

            // Handle HMOVE
            if (_hmove.HMoveEnabled)
            {
                // On the first time, set the latches and counters
                if (_hmove.HMoveJustStarted)
                {
                    _hmove.Player0Latch = true;
                    _hmove.Player0Cnt   = 0;

                    _hmove.Missile0Latch = true;
                    _hmove.Missile0Cnt   = 0;

                    _hmove.Player1Latch = true;
                    _hmove.Player1Cnt   = 0;

                    _hmove.Missile1Latch = true;
                    _hmove.Missile1Cnt   = 0;

                    _hmove.BallLatch = true;
                    _hmove.BallCnt   = 0;

                    _hmove.HMoveCnt = 0;

                    _hmove.HMoveJustStarted = false;
                    _hmove.LateHBlankReset  = true;
                    _hmove.DecCntEnabled    = false;
                }

                if (_hmove.DecCntEnabled)
                {
                    // Actually do stuff only evey 4 pulses
                    if (_hmove.HMoveCnt == 0)
                    {
                        // If the latch is still set
                        if (_hmove.Player0Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                // "Clock-Stuffing"
                                _player0.Tick();

                                // Increase by 1, max of 15
                                _hmove.Player0Cnt++;
                                _hmove.Player0Cnt %= 16;
                            }
                            else
                            {
                                _hmove.Player0Latch = false;
                            }
                        }

                        if (_hmove.Missile0Latch)
                        {
                            if (_hmove.Missile0Cnt == 15)
                            {
                            }

                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
                            {
                                // "Clock-Stuffing"
                                _player0.Missile.Tick();

                                // Increase by 1, max of 15
                                _hmove.Missile0Cnt++;
                                _hmove.Missile0Cnt %= 16;
                            }
                            else
                            {
                                _hmove.Missile0Latch = false;
                                _hmove.Missile0Cnt   = 0;
                            }
                        }

                        if (_hmove.Player1Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                // "Clock-Stuffing"
                                _player1.Tick();

                                // Increase by 1, max of 15
                                _hmove.Player1Cnt++;
                                _hmove.Player1Cnt %= 16;
                            }
                            else
                            {
                                _hmove.Player1Latch = false;
                            }
                        }

                        if (_hmove.Missile1Latch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
                            {
                                // "Clock-Stuffing"
                                _player1.Missile.Tick();

                                // Increase by 1, max of 15
                                _hmove.Missile1Cnt++;
                                _hmove.Missile1Cnt %= 16;
                            }
                            else
                            {
                                _hmove.Missile1Latch = false;
                            }
                        }

                        if (_hmove.BallLatch)
                        {
                            // If the move counter still has a bit in common with the HM register
                            if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F)
                            {
                                // "Clock-Stuffing"
                                _ball.Tick();

                                // Increase by 1, max of 15
                                _hmove.BallCnt++;
                                _hmove.BallCnt %= 16;
                            }
                            else
                            {
                                _hmove.BallLatch = false;
                            }
                        }

                        if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch)
                        {
                            _hmove.HMoveEnabled  = false;
                            _hmove.DecCntEnabled = false;
                            _hmove.HMoveDelayCnt = 0;
                        }
                    }

                    _hmove.HMoveCnt++;
                    _hmove.HMoveCnt %= 4;
                }

                if (_hmove.HMoveDelayCnt < 6)
                {
                    _hmove.HMoveDelayCnt++;
                }

                if (_hmove.HMoveDelayCnt == 6)
                {
                    _hmove.HMoveDelayCnt++;
                    _hmove.HMoveCnt      = 0;
                    _hmove.DecCntEnabled = true;
                }
            }

            // Increment the hsync counter
            _hsyncCnt++;
            _hsyncCnt %= 228;

            // End of the line? Add it to the buffer!
            if (_hsyncCnt == 0)
            {
                _hmove.LateHBlankReset = false;
                _CurrentScanLine++;
                LineCount++;
                _audioClocks += 2;                 // TODO: increment this at the appropriate places twice per line
            }
        }